From a0873df5cddb2e0908e25023513d34807539eb89 Mon Sep 17 00:00:00 2001 From: Jean-Marc Strauven Date: Sun, 27 Jul 2025 21:10:01 +0200 Subject: [PATCH] Remove user registration examples and validation command documentation - Deleted user registration flow examples including user-registration-example.md, user-registration-groups.php, user-registration-usage.php. - Removed validation command examples and usage documentation from validation-command-example.md and validation-examples.md. - Cleaned up unused code and examples to streamline the project structure. --- README.md | 367 +---- docs/README.md | 507 ------- docs/advanced-features.md | 334 ----- docs/commands.md | 512 ------- docs/configuration.md | 391 ------ docs/error-handling-usage.md | 407 ------ docs/error-handling.md | 709 ---------- docs/fixtures-examples.md | 232 ---- docs/namespace-resolution.md | 81 -- docs/queues.md | 491 ------- docs/step-groups.md | 365 ----- docs/testing.md | 1187 ----------------- docs/usage-examples.md | 375 ------ docs/validation.md | 350 ----- examples/README.md | 503 ------- examples/advanced-error-handling.php | 266 ---- examples/color-demo-example.php | 103 -- examples/complete-ecommerce-workflow.md | 365 ----- .../EmailVerificationEnabledCondition.php | 23 - .../WelcomeEmailEnabledCondition.php | 26 - examples/ecommerce-complete.yaml | 73 - .../ecommerce-order-processing-groups.php | 178 --- examples/ecommerce-order-processing.md | 421 ------ examples/error-handling-comprehensive.php | 541 -------- examples/flows/content-moderation.yaml | 42 - examples/flows/data-processing.yaml | 50 - examples/flows/ecommerce-order-groups.yaml | 73 - examples/flows/invalid-validation-demo.yaml | 24 - examples/flows/newsletter-campaign.yaml | 51 - examples/flows/order-processing.yaml | 55 - examples/flows/test-relative-namespace.yaml | 14 - examples/flows/user-registration-groups.yaml | 98 -- .../flows/user-registration-relative.yaml | 27 - examples/flows/user-registration.yaml | 50 - examples/flows/validation-demo.yaml | 47 - examples/groups-and-nested-flows.md | 485 ------- examples/groups/adult-user-processing.yaml | 9 - examples/groups/inventory-management.yaml | 31 - examples/groups/notifications.yaml | 31 - examples/groups/order-notifications.yaml | 31 - examples/groups/order-processing.yaml | 41 - examples/groups/order-validation.yaml | 31 - examples/groups/user-notifications.yaml | 31 - examples/groups/user-setup.yaml | 31 - examples/groups/user-validation.yaml | 31 - .../AssignDefaultRoleStep.php | 56 - .../CheckEmailUniquenessStep.php | 36 - .../CreateUserAccountStep.php | 48 - .../LogRegistrationEventStep.php | 45 - .../SendVerificationEmailStep.php | 61 - .../SendWelcomeEmailStep.php | 46 - .../SetupUserProfileStep.php | 60 - .../user-registration/ValidateInputStep.php | 33 - examples/test-enhanced-colors.php | 255 ---- examples/user-registration-example.md | 499 ------- examples/user-registration-groups.php | 261 ---- examples/user-registration-usage.php | 118 -- examples/validation-command-example.md | 209 --- examples/validation-examples.md | 255 ---- 59 files changed, 68 insertions(+), 12004 deletions(-) delete mode 100644 docs/README.md delete mode 100644 docs/advanced-features.md delete mode 100644 docs/commands.md delete mode 100644 docs/configuration.md delete mode 100644 docs/error-handling-usage.md delete mode 100644 docs/error-handling.md delete mode 100644 docs/fixtures-examples.md delete mode 100644 docs/namespace-resolution.md delete mode 100644 docs/queues.md delete mode 100644 docs/step-groups.md delete mode 100644 docs/testing.md delete mode 100644 docs/usage-examples.md delete mode 100644 docs/validation.md delete mode 100644 examples/README.md delete mode 100644 examples/advanced-error-handling.php delete mode 100644 examples/color-demo-example.php delete mode 100644 examples/complete-ecommerce-workflow.md delete mode 100644 examples/conditions/user-registration/EmailVerificationEnabledCondition.php delete mode 100644 examples/conditions/user-registration/WelcomeEmailEnabledCondition.php delete mode 100644 examples/ecommerce-complete.yaml delete mode 100644 examples/ecommerce-order-processing-groups.php delete mode 100644 examples/ecommerce-order-processing.md delete mode 100644 examples/error-handling-comprehensive.php delete mode 100644 examples/flows/content-moderation.yaml delete mode 100644 examples/flows/data-processing.yaml delete mode 100644 examples/flows/ecommerce-order-groups.yaml delete mode 100644 examples/flows/invalid-validation-demo.yaml delete mode 100644 examples/flows/newsletter-campaign.yaml delete mode 100644 examples/flows/order-processing.yaml delete mode 100644 examples/flows/test-relative-namespace.yaml delete mode 100644 examples/flows/user-registration-groups.yaml delete mode 100644 examples/flows/user-registration-relative.yaml delete mode 100644 examples/flows/user-registration.yaml delete mode 100644 examples/flows/validation-demo.yaml delete mode 100644 examples/groups-and-nested-flows.md delete mode 100644 examples/groups/adult-user-processing.yaml delete mode 100644 examples/groups/inventory-management.yaml delete mode 100644 examples/groups/notifications.yaml delete mode 100644 examples/groups/order-notifications.yaml delete mode 100644 examples/groups/order-processing.yaml delete mode 100644 examples/groups/order-validation.yaml delete mode 100644 examples/groups/user-notifications.yaml delete mode 100644 examples/groups/user-setup.yaml delete mode 100644 examples/groups/user-validation.yaml delete mode 100644 examples/steps/user-registration/AssignDefaultRoleStep.php delete mode 100644 examples/steps/user-registration/CheckEmailUniquenessStep.php delete mode 100644 examples/steps/user-registration/CreateUserAccountStep.php delete mode 100644 examples/steps/user-registration/LogRegistrationEventStep.php delete mode 100644 examples/steps/user-registration/SendVerificationEmailStep.php delete mode 100644 examples/steps/user-registration/SendWelcomeEmailStep.php delete mode 100644 examples/steps/user-registration/SetupUserProfileStep.php delete mode 100644 examples/steps/user-registration/ValidateInputStep.php delete mode 100755 examples/test-enhanced-colors.php delete mode 100644 examples/user-registration-example.md delete mode 100644 examples/user-registration-groups.php delete mode 100644 examples/user-registration-usage.php delete mode 100644 examples/validation-command-example.md delete mode 100644 examples/validation-examples.md diff --git a/README.md b/README.md index 67e569d..2440fc4 100644 --- a/README.md +++ b/README.md @@ -1,54 +1,41 @@ # Laravel Flowpipe -Laravel Flowpipe - -Composable, traceable and declarative Flow Pipelines for Laravel. A modern alternative to Laravel's Pipeline, with support for conditional steps, nested flows, tracing, validation, and more. - -[![Latest Version](https://img.shields.io/packagist/v/grazulex/laravel-flowpipe.svg?style=flat-square)](https://packagist.org/packages/grazulex/laravel-flowpipe) -[![Total Downloads](https://img.shields.io/packagist/dt/grazulex/laravel-flowpipe.svg?style=flat-square)](https://packagist.org/packages/grazulex/laravel-flowpipe) -[![License](https://img.shields.io/github/license/grazulex/laravel-flowpipe.svg?style=flat-square)](https://github.com/Grazulex/laravel-flowpipe/blob/main/LICENSE.md) -[![PHP Version](https://img.shields.io/packagist/php-v/grazulex/laravel-flowpipe.svg?style=flat-square)](https://php.net/) -[![Laravel Version](https://img.shields.io/badge/laravel-12.x-ff2d20?style=flat-square&logo=laravel)](https://laravel.com/) -[![Tests](https://img.shields.io/github/actions/workflow/status/grazulex/laravel-flowpipe/tests.yml?branch=main&label=tests&style=flat-square)](https://github.com/Grazulex/laravel-flowpipe/actions) -[![Code Style](https://img.shields.io/badge/code%20style-pint-000000?style=flat-square&logo=laravel)](https://github.com/laravel/pint) - -## ๐Ÿ“– Table of Contents - -- [Overview](#overview) -- [โœจ Features](#-features) -- [๐Ÿ“ฆ Installation](#-installation) -- [๐Ÿš€ Quick Start](#-quick-start) -- [๐Ÿ›ก๏ธ Error Handling](#๏ธ-error-handling) -- [๐Ÿ”— Step Groups & Nested Flows](#-step-groups--nested-flows) -- [๐Ÿ“‹ YAML Flow Definitions](#-yaml-flow-definitions) -- [โš™๏ธ Configuration](#๏ธ-configuration) -- [๐Ÿ“š Documentation](#-documentation) -- [๐Ÿ’ก Examples](#-examples) -- [๐Ÿงช Testing](#-testing) -- [๐Ÿ”ง Requirements](#-requirements) -- [๐Ÿš€ Performance](#-performance) -- [๐Ÿค Contributing](#-contributing) -- [๐Ÿ”’ Security](#-security) -- [๐Ÿ“„ License](#-license) - -## Overview - -Laravel Flowpipe is a powerful, modern alternative to Laravel's built-in Pipeline package that provides composable, traceable, and declarative flow pipelines. Perfect for building complex business workflows, data processing pipelines, and API integrations with advanced error handling and retry mechanisms. - -## โœจ Features - -- ๐Ÿš€ **Fluent API** - Chainable, expressive syntax for building pipelines -- ๐Ÿ”„ **Flexible Steps** - Support for closures, classes, and custom steps +
+ Laravel Flowpipe + + **Composable, traceable and declarative Flow Pipelines for Laravel** + + *A modern alternative to Laravel's Pipeline with advanced features for complex workflow management* + + [![Latest Version](https://img.shields.io/packagist/v/grazulex/laravel-flowpipe.svg?style=flat-square)](https://packagist.org/packages/grazulex/laravel-flowpipe) + [![Total Downloads](https://img.shields.io/packagist/dt/grazulex/laravel-flowpipe.svg?style=flat-square)](https://packagist.org/packages/grazulex/laravel-flowpipe) + [![License](https://img.shields.io/github/license/grazulex/laravel-flowpipe.svg?style=flat-square)](https://github.com/Grazulex/laravel-flowpipe/blob/main/LICENSE.md) + [![PHP Version](https://img.shields.io/packagist/php-v/grazulex/laravel-flowpipe.svg?style=flat-square)](https://php.net/) + [![Laravel Version](https://img.shields.io/badge/laravel-12.x-ff2d20?style=flat-square&logo=laravel)](https://laravel.com/) + [![Tests](https://img.shields.io/github/actions/workflow/status/grazulex/laravel-flowpipe/tests.yml?branch=main&label=tests&style=flat-square)](https://github.com/Grazulex/laravel-flowpipe/actions) + [![Code Style](https://img.shields.io/badge/code%20style-pint-000000?style=flat-square&logo=laravel)](https://github.com/laravel/pint) +
+ +--- + +## ๐Ÿš€ Overview + +Laravel Flowpipe is a powerful, modern alternative to Laravel's built-in Pipeline package that provides **composable**, **traceable**, and **declarative** flow pipelines. Perfect for building complex business workflows, data processing pipelines, user registration flows, API integrations, and any scenario where you need reliable, maintainable, and testable step-by-step processing. + +## โœจ Key Features + +- ๐Ÿš€ **Fluent API** - Chainable, expressive syntax +- ๐Ÿ”„ **Flexible Steps** - Support for closures, classes, and custom steps - ๐ŸŽฏ **Conditional Logic** - Built-in conditional step execution - ๐Ÿ“Š **Tracing & Debugging** - Track execution flow and performance -- ๐Ÿ›ก๏ธ **Error Handling** - Comprehensive retry, fallback, and compensation strategies -- ๏ฟฝ **Step Groups** - Reusable, named collections of steps -- ๐ŸŽฏ **Nested Flows** - Create isolated sub-workflows +- ๐Ÿ›ก๏ธ **Advanced Error Handling** - Retry, fallback, and compensation strategies +- ๐Ÿ”— **Step Groups** - Reusable, named collections of steps +- ๐ŸŽฏ **Nested Flows** - Create isolated sub-workflows for complex logic - ๐Ÿ“‹ **YAML Support** - Define flows in YAML for easy configuration - ๐Ÿงช **Test-Friendly** - Built-in test tracer for easy testing -- โšก **Performance** - Optimized for speed and memory efficiency - ๐ŸŽจ **Artisan Commands** - Full CLI support for flow management -- โœ… **Flow Validation** - Validate flow definitions with error reporting +- โœ… **Flow Validation** - Validate flow definitions with comprehensive error reporting +- ๐Ÿ“ˆ **Export & Documentation** - Export to JSON, Mermaid, and Markdown ## ๐Ÿ“ฆ Installation @@ -58,18 +45,9 @@ Install the package via Composer: composer require grazulex/laravel-flowpipe ``` -> **๐Ÿ’ก Auto-Discovery** -> The service provider will be automatically registered thanks to Laravel's package auto-discovery. +> **๐Ÿ’ก Auto-Discovery**: The service provider will be automatically registered thanks to Laravel's package auto-discovery. -Publish configuration: - -```bash -php artisan vendor:publish --tag=flowpipe-config -``` - -## ๐Ÿš€ Quick Start - -### 1. Create a Basic Pipeline +## โšก Quick Start ```php use Grazulex\LaravelFlowpipe\Flowpipe; @@ -86,270 +64,61 @@ $result = Flowpipe::make() // Result: "HELLO-WORLD!" ``` -### 2. Using Factory Methods - -```php -use Grazulex\LaravelFlowpipe\Flowpipe; - -// Create with debug tracing -$debugFlow = Flowpipe::debug(true, 'flowpipe'); -$result = $debugFlow - ->send($data) - ->through($steps) - ->thenReturn(); - -// Create with performance tracing -$perfFlow = Flowpipe::performance(); -$result = $perfFlow - ->send($data) - ->through($steps) - ->thenReturn(); - -// Create with test tracing (for unit tests) -$testFlow = Flowpipe::test(); -$result = $testFlow - ->send($data) - ->through($steps) - ->thenReturn(); -``` - -### 3. Using Step Groups - -```php -// Define reusable step groups -Flowpipe::group('text-processing', [ - fn($data, $next) => $next(trim($data)), - fn($data, $next) => $next(strtoupper($data)), - fn($data, $next) => $next(str_replace(' ', '-', $data)), -]); - -// Use groups in flows -$result = Flowpipe::make() - ->send(' hello world ') - ->useGroup('text-processing') - ->through([ - fn($data, $next) => $next($data . '!'), - ]) - ->thenReturn(); - -// Result: "HELLO-WORLD!" -``` - -### 4. Using Error Handling - -```php -// Exponential backoff retry -$result = Flowpipe::make() - ->send(['api_url' => 'https://api.example.com/data']) - ->exponentialBackoff(3, 100, 2.0) // 3 attempts, 100ms base delay, 2x multiplier - ->through([ - fn($data, $next) => $next(callExternalAPI($data['api_url'])), - fn($data, $next) => $next(processAPIResponse($data)), - ]) - ->thenReturn(); -``` - -## ๐Ÿ›ก๏ธ Error Handling - -Laravel Flowpipe provides comprehensive error handling strategies: - -```php -// Exponential backoff retry -$result = Flowpipe::make() - ->send($userData) - ->exponentialBackoff(3, 100, 2.0) // 3 attempts, 100ms base delay, 2x multiplier - ->through([ - fn($data, $next) => $next(saveToDatabase($data)), - ]) - ->thenReturn(); - -// Simple fallback with default value -$result = Flowpipe::make() - ->send(['user_id' => 123]) - ->withFallback(fn($payload, $error) => ['cached_data' => true]) - ->through([ - fn($data, $next) => $next(fetchUserProfile($data['user_id'])), - ]) - ->thenReturn(); -``` - -## ๐Ÿ”— Step Groups & Nested Flows - -Laravel Flowpipe supports reusable step groups and nested flows for better organization: - -### Step Groups - -Define reusable groups of steps: - -```php -// Define reusable step groups -Flowpipe::group('user-validation', [ - fn($user, $next) => $next(filter_var($user['email'], FILTER_VALIDATE_EMAIL) ? $user : throw new InvalidArgumentException('Invalid email')), - fn($user, $next) => $next(strlen($user['name']) > 0 ? $user : throw new InvalidArgumentException('Name required')), -]); - -Flowpipe::group('notifications', [ - fn($user, $next) => $next(array_merge($user, ['email_sent' => true])), - fn($user, $next) => $next(array_merge($user, ['logged' => true])), -]); - -// Use groups in flows -$result = Flowpipe::make() - ->send(['email' => 'user@example.com', 'name' => 'John Doe']) - ->useGroup('user-validation') - ->useGroup('notifications') - ->thenReturn(); -``` - -### Nested Flows - -Create isolated sub-workflows: - -```php -$result = Flowpipe::make() - ->send('hello world') - ->nested([ - // This nested flow runs independently - fn($data, $next) => $next(strtoupper($data)), - fn($data, $next) => $next(str_replace(' ', '-', $data)), - ]) - ->through([ - // Main flow continues with nested result - fn($data, $next) => $next($data . '!'), - ]) - ->thenReturn(); - -// Result: "HELLO-WORLD!" -``` - -## ๐Ÿ“‹ YAML Flow Definitions - -Create flow definitions in YAML for easy configuration: - -```yaml -# flow_definitions/user_processing.yaml -flow: UserProcessingFlow -description: Process user data with validation - -send: - name: "John Doe" - email: "john@example.com" - -steps: - - type: group - name: user-validation - - type: nested - steps: - - type: closure - action: uppercase - - type: group - name: notifications -``` - -```bash -# Run flows via Artisan commands -php artisan flowpipe:run user_processing -php artisan flowpipe:export user_processing --format=mermaid -``` - -## ๐Ÿ’ก Examples - -### Basic Text Processing - -```php -$result = Flowpipe::make() - ->send(' hello world ') - ->through([ - fn($text, $next) => $next(trim($text)), - fn($text, $next) => $next(ucwords($text)), - fn($text, $next) => $next(str_replace(' ', '-', $text)), - ]) - ->thenReturn(); - -// Result: "Hello-World" -``` - -### User Registration Flow - -```php -use App\Flowpipe\Steps\ValidateUserStep; -use App\Flowpipe\Steps\SendWelcomeEmailStep; - -$user = Flowpipe::make() - ->send($userData) - ->through([ - new ValidateUserStep(), - new SendWelcomeEmailStep(), - ]) - ->thenReturn(); -``` - -Check out the [examples directory](examples) for more examples. - -## ๐Ÿงช Testing +## ๐Ÿ”ง Requirements -Laravel Flowpipe includes a test tracer for easy testing: +- **PHP 8.3+** +- **Laravel 12.0+** -```php -use Grazulex\LaravelFlowpipe\Tracer\TestTracer; - -public function test_user_processing_flow() -{ - $result = Flowpipe::test() - ->send(['name' => 'John']) - ->through([ - fn($data, $next) => $next(strtoupper($data['name'])), - ]) - ->thenReturn(); - - $this->assertEquals('JOHN', $result); -} -``` +## ๐Ÿ“š Complete Documentation -## ๐Ÿ“š Documentation +For comprehensive documentation, examples, and advanced usage guides, visit our **Wiki**: -For detailed documentation, examples, and advanced usage: +### ๏ฟฝ **[๐Ÿ‘‰ Laravel Flowpipe Wiki](https://github.com/Grazulex/laravel-flowpipe/wiki)** -- ๐Ÿ“š [Full Documentation](docs/README.md) -- ๐ŸŽฏ [Examples](examples/README.md) -- ๐Ÿ”ง [Configuration](docs/configuration.md) -- ๐Ÿงช [Testing](docs/testing.md) -- ๐Ÿ›ก๏ธ [Error Handling](docs/error-handling.md) +The wiki includes: -## ๐Ÿ”ง Requirements +- **๐Ÿ“š [Getting Started Guide](https://github.com/Grazulex/laravel-flowpipe/wiki/Getting-Started)** +- **๐Ÿ›ก๏ธ [Error Handling & Strategies](https://github.com/Grazulex/laravel-flowpipe/wiki/Error-Handling)** +- **๐Ÿ”— [Step Groups & Nested Flows](https://github.com/Grazulex/laravel-flowpipe/wiki/Step-Groups-and-Nested-Flows)** +- **๏ฟฝ [YAML Flow Definitions](https://github.com/Grazulex/laravel-flowpipe/wiki/YAML-Flow-Definitions)** +- **๐ŸŽจ [Artisan Commands](https://github.com/Grazulex/laravel-flowpipe/wiki/Artisan-Commands)** +- **๐Ÿงช [Testing Guide](https://github.com/Grazulex/laravel-flowpipe/wiki/Testing)** +- **๏ฟฝ [Examples & Use Cases](https://github.com/Grazulex/laravel-flowpipe/wiki/Examples)** +- **๏ฟฝ [API Reference](https://github.com/Grazulex/laravel-flowpipe/wiki/API-Reference)** -- PHP: ^8.3 -- Laravel: ^12.0 -- Carbon: ^3.10 -- Symfony YAML: ^7.3 +--- ## ๐Ÿค Contributing -We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details. +Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details. ## ๐Ÿ”’ Security -If you discover a security vulnerability, please review our [Security Policy](SECURITY.md) before disclosing it. +If you discover any security-related issues, please email **jms@grazulex.be** instead of using the issue tracker. + +## ๐Ÿ“ Changelog + +Please see [RELEASES.md](RELEASES.md) for more information on what has changed recently. ## ๐Ÿ“„ License -Laravel Flowpipe is open-sourced software licensed under the [MIT license](LICENSE.md). +The MIT License (MIT). Please see [License File](LICENSE.md) for more information. ---- +## ๐Ÿ‘ฅ Credits -**Made with โค๏ธ for the Laravel community** +- **[Jean-Marc Strauven](https://github.com/Grazulex)** +- **[All Contributors](../../contributors)** -### Resources +## ๐Ÿ’ฌ Support -- [๐Ÿ“– Documentation](docs/README.md) -- [๐Ÿ’ฌ Discussions](https://github.com/Grazulex/laravel-flowpipe/discussions) -- [๐Ÿ› Issue Tracker](https://github.com/Grazulex/laravel-flowpipe/issues) -- [๐Ÿ“ฆ Packagist](https://packagist.org/packages/grazulex/laravel-flowpipe) +- ๐Ÿ› **[Report Issues](https://github.com/Grazulex/laravel-flowpipe/issues)** +- ๏ฟฝ **[Discussions](https://github.com/Grazulex/laravel-flowpipe/discussions)** +- ๏ฟฝ **[Documentation](https://github.com/Grazulex/laravel-flowpipe/wiki)** -### Community Links +--- -- [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) - Our code of conduct -- [CONTRIBUTING.md](CONTRIBUTING.md) - How to contribute -- [SECURITY.md](SECURITY.md) - Security policy -- [RELEASES.md](RELEASES.md) - Release notes and changelog +
+ Laravel Flowpipe - A modern, powerful alternative to Laravel's built-in Pipeline
+ with enhanced features for complex workflow management. +
diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 239c384..0000000 --- a/docs/README.md +++ /dev/null @@ -1,507 +0,0 @@ -# Laravel Flowpipe Documentation - -Welcome to the comprehensive documentation for Laravel Flowpipe - a modern, composable, and traceable flow pipeline system for Laravel applications. - -## Table of Contents - -1. [Getting Started](#getting-started) -2. [Core Concepts](#core-concepts) -3. [Step Groups & Nested Flows](#step-groups--nested-flows) -4. [Configuration](#configuration) -5. [YAML Flow Definitions](#yaml-flow-definitions) -6. [Artisan Commands](#artisan-commands) -7. [Testing](#testing) -8. [Advanced Usage](#advanced-usage) -9. [Integrations](#integrations) -10. [Performance](#performance) -11. [Examples](#examples) - -## Getting Started - -### Installation - -```bash -composer require grazulex/laravel-flowpipe -``` - -### Basic Usage - -```php -use Grazulex\LaravelFlowpipe\Flowpipe; - -$result = Flowpipe::make() - ->send('Hello World') - ->through([ - fn($data, $next) => $next(strtoupper($data)), - fn($data, $next) => $next(str_replace(' ', '-', $data)), - ]) - ->thenReturn(); - -// Result: "HELLO-WORLD" -``` - -## Core Concepts - -### Flowpipe - -The `Flowpipe` class is the main entry point for creating and executing flow pipelines. It provides a fluent API for building complex data processing flows. - -### Steps - -Steps are the building blocks of flows. They can be: -- **Closures**: Anonymous functions for simple operations -- **Classes**: Custom step classes implementing the `FlowStep` interface -- **Callables**: Any callable PHP construct - -### Conditional Logic - -Laravel Flowpipe supports conditional execution through the `ConditionalStep` class: - -```php -use Grazulex\LaravelFlowpipe\Steps\ConditionalStep; -use Grazulex\LaravelFlowpipe\Contracts\Condition; - -class IsActiveCondition implements Condition -{ - public function evaluate(mixed $payload): bool - { - return is_array($payload) && ($payload['active'] ?? false); - } -} - -$result = Flowpipe::make() - ->send(['active' => true, 'name' => 'John']) - ->through([ - fn($data, $next) => $next($data['name']), - ConditionalStep::when( - new IsActiveCondition(), - fn($name, $next) => $next(strtoupper($name)) - ), - ConditionalStep::unless( - new IsActiveCondition(), - fn($name, $next) => $next(strtolower($name)) - ), - ]) - ->thenReturn(); -``` - -## Step Groups & Nested Flows - -Laravel Flowpipe now supports **Step Groups** and **Nested Flows** for better organization and modularity. - -### Step Groups - -Define reusable collections of steps that can be referenced by name: - -```php -use Grazulex\LaravelFlowpipe\Flowpipe; - -// Define a reusable group -Flowpipe::group('user-validation', [ - fn($user, $next) => $next(filter_var($user['email'], FILTER_VALIDATE_EMAIL) ? $user : throw new InvalidArgumentException('Invalid email')), - fn($user, $next) => $next(strlen($user['password']) >= 8 ? $user : throw new InvalidArgumentException('Password too short')), - fn($user, $next) => $next(strlen($user['name']) > 0 ? $user : throw new InvalidArgumentException('Name required')), -]); - -// Use the group in flows -$result = Flowpipe::make() - ->send(['email' => 'user@example.com', 'password' => 'securepass', 'name' => 'John']) - ->useGroup('user-validation') - ->through([ - fn($user, $next) => $next(array_merge($user, ['id' => uniqid()])), - ]) - ->thenReturn(); -``` - -### Nested Flows - -Create isolated sub-workflows that run independently: - -```php -$result = Flowpipe::make() - ->send(['name' => 'John Doe', 'email' => 'john@example.com']) - ->nested([ - // This nested flow runs independently - fn($data, $next) => $next(array_merge($data, ['processed' => true])), - fn($data, $next) => $next(array_merge($data, ['id' => uniqid()])), - ]) - ->through([ - // Main flow continues here - fn($data, $next) => $next(array_merge($data, ['completed' => true])), - ]) - ->thenReturn(); -``` - -### Combining Groups and Nested Flows - -```php -// Define groups -Flowpipe::group('validation', [ - fn($data, $next) => $next(/* validation logic */), -]); - -Flowpipe::group('notifications', [ - fn($data, $next) => $next(/* notification logic */), -]); - -// Use in complex flows -$result = Flowpipe::make() - ->send($userData) - ->useGroup('validation') - ->nested([ - // Complex processing in isolation - fn($data, $next) => $next(/* complex processing */), - ]) - ->useGroup('notifications') - ->thenReturn(); -``` - -### Group Management - -```php -// Check if a group exists -if (Flowpipe::hasGroup('user-validation')) { - // Use the group -} - -// Get all registered groups -$groups = Flowpipe::getGroups(); - -// Clear all groups (useful for testing) -Flowpipe::clearGroups(); -``` - -For comprehensive examples and advanced usage, see the [Step Groups Guide](step-groups.md). - -### Tracing - -Tracing allows you to monitor and debug flow execution: -- `BasicTracer` - For development and debugging -- `TestTracer` - For testing and assertions -- Custom tracers implementing the `Tracer` interface - -## Configuration - -Publish the configuration file: - -```bash -php artisan vendor:publish --provider="Grazulex\LaravelFlowpipe\LaravelFlowpipeServiceProvider" -``` - -Configuration options: - -```php -return [ - 'definitions_path' => 'flow_definitions', - 'trace_enabled' => env('FLOWPIPE_TRACE_ENABLED', false), - 'cache_enabled' => env('FLOWPIPE_CACHE_ENABLED', true), -]; -``` - -## YAML Flow Definitions - -Create reusable flow definitions in YAML format, including support for groups and nested flows: - -```yaml -flow: UserProcessingFlow -description: Process user data with validation and notifications - -send: - name: "John Doe" - email: "john@example.com" - password: "securepass123" - -steps: - # Use a predefined group - - type: group - name: user-validation - - # Create a nested flow - - type: nested - steps: - - type: closure - action: hash_password - - type: closure - action: remove_plain_password - - # Continue with main flow - - type: closure - action: create_user_record - - # Use another group - - type: group - name: user-notifications -``` - -### Group Definitions - -Define groups in separate YAML files: - -```yaml -# groups/user-validation.yaml -group: user-validation -description: Validate user input data -steps: - - type: closure - action: validate_email - - type: closure - action: validate_password_strength - - type: closure - action: validate_required_fields -``` - -### Supported Step Types - -- `action` - Execute an action (legacy alias for `step`) -- `closure` - Execute a closure action -- `step` - Execute a custom step class -- `condition` - Conditional execution -- `group` - Execute a predefined group -- `nested` - Execute a nested flow - -### Supported Operators - -- `equals` - Exact match -- `contains` - String contains -- `greater_than` - Numeric comparison -- `less_than` - Numeric comparison -- `in` - Array contains value - -## Artisan Commands - -Laravel Flowpipe provides comprehensive CLI support: - -### List Flows - -```bash -php artisan flowpipe:list -php artisan flowpipe:list --detailed -``` - -### Run Flows - -```bash -php artisan flowpipe:run user_processing -php artisan flowpipe:run user_processing --payload='{"name": "John"}' -``` - -### Export Flows - -```bash -php artisan flowpipe:export user_processing --format=json -php artisan flowpipe:export user_processing --format=mermaid -php artisan flowpipe:export user_processing --format=md --output=docs/user_processing.md -``` - -### Validate Flows - -Validate YAML flow definitions for syntax errors and structural issues: - -```bash -# Validate a specific flow -php artisan flowpipe:validate user_processing - -# Validate all flows -php artisan flowpipe:validate --all - -# Validate flows from a specific path -php artisan flowpipe:validate --all --path=custom/flow/path - -# Get JSON output format -php artisan flowpipe:validate --all --format=json -``` - -The validation command checks for: -- **YAML syntax errors**: Invalid YAML format -- **Required fields**: Missing flow name or steps -- **Step structure**: Valid step types and required fields -- **References**: Existing groups and step classes -- **Condition syntax**: Valid operators and structure -- **Nested flows**: Proper nesting structure - -### Generate Flows - -```bash -php artisan flowpipe:make-flow NewUserFlow --template=basic -php artisan flowpipe:make-flow ComplexFlow --template=conditional -php artisan flowpipe:make-flow AdvancedFlow --template=advanced -``` - -### Generate Steps - -```bash -php artisan flowpipe:make-step ProcessUserStep -``` - -## Testing - -Laravel Flowpipe includes robust testing support: - -```php -use Grazulex\LaravelFlowpipe\Tracer\TestTracer; - -public function test_user_processing_flow() -{ - $tracer = new TestTracer(); - - $result = Flowpipe::make() - ->send(['name' => 'John']) - ->through([ - fn($data, $next) => $next(strtoupper($data['name'])), - ]) - ->withTracer($tracer) - ->thenReturn(); - - $this->assertEquals('JOHN', $result); - $this->assertCount(1, $tracer->count()); -} -``` - -## Advanced Usage - -### Custom Step Classes - -```php -use Grazulex\LaravelFlowpipe\Contracts\FlowStep; - -class ValidateUserStep implements FlowStep -{ - public function handle(mixed $payload, \Closure $next): mixed - { - if (!is_array($payload) || !isset($payload['email'])) { - throw new \InvalidArgumentException('Email is required'); - } - - return $next($payload); - } -} -``` - -### Nested Flows - -```php -$result = Flowpipe::make() - ->send($userData) - ->through([ - new ValidateUserStep(), - function ($data, $next) { - return Flowpipe::make() - ->send($data) - ->through([ - new ProcessUserStep(), - new EnrichUserStep(), - ]) - ->thenReturn(); - }, - new NotifyUserStep(), - ]) - ->thenReturn(); -``` - -### Custom Tracers - -```php -use Grazulex\LaravelFlowpipe\Contracts\Tracer; - -class DatabaseTracer implements Tracer -{ - public function trace(string $stepClass, mixed $payloadBefore, mixed $payloadAfter, ?float $durationMs = null): void - { - // Store trace data in database - } -} -``` - -## Integrations - -### Laravel Queues - -Flowpipe works seamlessly with Laravel's queue system without any modifications. You can easily run your flows asynchronously by wrapping them in Laravel jobs. - -```php -// In a Laravel Job -class ProcessOrderJob implements ShouldQueue -{ - public function handle(): void - { - $result = Flowpipe::make() - ->send($this->orderData) - ->through([ - ValidateOrderStep::class, - ProcessPaymentStep::class, - UpdateInventoryStep::class, - ]) - ->thenReturn(); - } -} - -// Dispatch the job -dispatch(new ProcessOrderJob($orderData))->onQueue('orders'); -``` - -For comprehensive queue integration patterns, batching, error handling, and best practices, see the [Queue Integration Guide](queues.md). - -## Performance - -### Optimization Tips - -1. **Use Lazy Evaluation**: Steps are only executed when needed -2. **Enable Caching**: Cache flow definitions for better performance -3. **Minimize Tracing**: Only enable tracing when needed -4. **Batch Operations**: Process multiple items in single steps - -### Benchmarks - -Laravel Flowpipe is optimized for performance: -- **Memory Usage**: < 1MB for complex flows -- **Execution Time**: < 1ms overhead per step -- **Scalability**: Handles 1000+ steps efficiently - -## Examples - -See the [examples](../examples/README.md) directory for comprehensive examples and use cases. - -## API Reference - -### Flowpipe Methods - -- `make()` - Create a new flowpipe instance -- `send($data)` - Set initial data -- `through(array $steps)` - Add steps to the pipeline -- `useGroup(string $name)` - Add a predefined group to the pipeline -- `nested(array $steps)` - Create a nested flow -- `cache($key, $ttl, $store)` - Add cache step -- `retry($maxAttempts, $delayMs, $shouldRetry)` - Add retry step -- `rateLimit($key, $maxAttempts, $decayMinutes, $keyGenerator)` - Add rate limit step -- `transform($transformer)` - Add transform step -- `validate($rules, $messages, $customAttributes)` - Add validation step -- `batch($batchSize, $preserveKeys)` - Add batch step -- `withTracer(Tracer $tracer)` - Add a tracer -- `thenReturn()` - Execute and return result -- `context()` - Get flow context - -### Static Methods - -- `group(string $name, array $steps)` - Define a reusable step group -- `hasGroup(string $name)` - Check if a group exists -- `getGroups()` - Get all registered groups -- `clearGroups()` - Clear all registered groups (useful for testing) - -### Conditional Steps - -- `ConditionalStep::when($condition, $step)` - Execute step when condition is true -- `ConditionalStep::unless($condition, $step)` - Execute step when condition is false - -### Tracer Methods - -- `trace($stepClass, $before, $after, $duration)` - Trace step execution -- `all()` - Get all trace logs -- `steps()` - Get all step names -- `count()` - Get number of traced steps -- `firstStep()` - Get first step name -- `lastStep()` - Get last step name -- `clear()` - Clear all traces - ---- - -For more examples and advanced usage patterns, see the [examples](../examples/README.md) directory. diff --git a/docs/advanced-features.md b/docs/advanced-features.md deleted file mode 100644 index f43cef2..0000000 --- a/docs/advanced-features.md +++ /dev/null @@ -1,334 +0,0 @@ -# Advanced Laravel Flowpipe Features - -This document showcases the advanced features available in Laravel Flowpipe. - -## New Steps Available - -### 1. Cache Step -Cache the results of expensive operations: - -```php -use Grazulex\LaravelFlowpipe\Flowpipe; - -$result = Flowpipe::make() - ->send($expensiveData) - ->cache('my-cache-key', 3600) // Cache for 1 hour - ->through([ - fn($data, $next) => $next(expensiveOperation($data)), - ]) - ->thenReturn(); -``` - -### 2. Retry Step -Automatically retry failed operations: - -```php -$result = Flowpipe::make() - ->send($unreliableData) - ->retry(3, 100) // 3 attempts, 100ms delay - ->through([ - fn($data, $next) => $next(unreliableApiCall($data)), - ]) - ->thenReturn(); - -// With custom retry logic -$result = Flowpipe::make() - ->send($data) - ->retry(5, 200, function ($exception) { - // Only retry on specific exceptions - return $exception instanceof ConnectionException; - }) - ->through([ - fn($data, $next) => $next(networkOperation($data)), - ]) - ->thenReturn(); -``` - -### 3. Rate Limiting Step -Limit the rate of operations: - -```php -$result = Flowpipe::make() - ->send($apiData) - ->rateLimit('api-calls', 60, 1) // 60 calls per minute - ->through([ - fn($data, $next) => $next(apiCall($data)), - ]) - ->thenReturn(); - -// With custom key generation -$result = Flowpipe::make() - ->send($userData) - ->rateLimit('user-ops', 10, 1, fn($data) => $data['user_id']) - ->through([ - fn($data, $next) => $next(userOperation($data)), - ]) - ->thenReturn(); -``` - -### 4. Transform Step -Transform data with helper methods: - -```php -use Grazulex\LaravelFlowpipe\Steps\TransformStep; - -// Basic transformation -$result = Flowpipe::make() - ->send($data) - ->transform(fn($data) => strtoupper($data)) - ->thenReturn(); -``` - ->through([ - TransformStep::filter(fn($user) => $user['active']), - ]) - ->thenReturn(); - -// Pluck specific fields -$result = Flowpipe::make() - ->send($users) - ->through([ - TransformStep::pluck('name'), - ]) - ->thenReturn(); -``` - -### 5. Validation Step -Validate data using Laravel's validation: - -```php -use Grazulex\LaravelFlowpipe\Steps\ValidationStep; - -// Example with array payload (returns validated array) -$result = Flowpipe::make() - ->send(['email' => 'foo@bar.com', 'name' => 'John']) - ->validate([ - 'email' => 'required|email', - 'name' => 'required|string|max:255', - ]) - ->through([ - fn($data, $next) => $next(array_merge($data, ['processed' => true])), - ]) - ->thenReturn(); - -// Example with scalar payload and one rule -$result = Flowpipe::make() - ->send('foo@bar.com') - ->validate(['email' => 'required|email']) - ->thenReturn(); -``` - -### 6. Batch Step -Process data in batches: - -```php -$result = Flowpipe::make() - ->send($largeDataset) - ->batch(50) // Process 50 items at a time - ->through([ - fn($batch, $next) => $next(processBatch($batch)), - ]) - ->thenReturn(); -``` - -## Advanced Tracers - -### 1. Debug Tracer -Comprehensive debugging with console output and logging: - -```php -use Grazulex\LaravelFlowpipe\Flowpipe; - -$result = Flowpipe::debug(true, 'flowpipe') // Log to file - ->send($data) - ->through([ - SlowStep::class, - FastStep::class, - ]) - ->thenReturn(); - -// Manual usage -$tracer = new \Grazulex\LaravelFlowpipe\Tracer\DebugTracer(); -$result = Flowpipe::make($tracer) - ->send($data) - ->through($steps) - ->thenReturn(); - -// Get detailed statistics -$tracer->printSummary(); -``` - -### 2. Performance Tracer -Monitor performance and identify bottlenecks: - -```php -$result = Flowpipe::performance() - ->send($data) - ->through([ - ExpensiveStep::class, - MemoryHeavyStep::class, - ]) - ->thenReturn(); - -// Get performance metrics -$tracer = $result->context()->tracer(); -$report = $tracer->getPerformanceReport(); - -if ($tracer->hasPerformanceIssues()) { - $bottlenecks = $tracer->getBottlenecks(); - // Handle performance issues -} -``` - -### 3. Database Tracer -Store execution traces in database: - -```php -// First, create the table -\Grazulex\LaravelFlowpipe\Tracer\DatabaseTracer::createTable(); - -$result = Flowpipe::database('my_traces') - ->send($data) - ->through($steps) - ->thenReturn(); - -// Get traces for this execution -$tracer = $result->context()->tracer(); -$traces = $tracer->getExecutionTraces(); - -// Cleanup old traces -DatabaseTracer::cleanup(30); // Remove traces older than 30 days -``` - -## Complex Example: E-commerce Order Processing - -```php -use Grazulex\LaravelFlowpipe\Flowpipe; -use Grazulex\LaravelFlowpipe\Steps\ValidationStep; - -class OrderProcessingPipeline -{ - public function process(array $orderData) - { - return Flowpipe::debug(true, 'orders') - ->send($orderData) - - // Validate order data - ->validate([ - 'user_id' => 'required|exists:users,id', - 'items' => 'required|array|min:1', - 'payment_method' => 'required|in:credit_card,paypal', - ]) - - // Cache expensive user lookup - ->cache("user-{$orderData['user_id']}", 300) - - // Rate limit orders per user - ->rateLimit('orders', 5, 1, fn($data) => $data['user_id']) - - // Process in batches if many items - ->batch(10) - - // Retry payment processing - ->retry(3, 1000, fn($e) => $e instanceof PaymentException) - - // Transform and process - ->through([ - LoadUserStep::class, - ValidateInventoryStep::class, - CalculateTotalStep::class, - ProcessPaymentStep::class, - CreateOrderStep::class, - SendConfirmationStep::class, - ]) - - ->thenReturn(); - } -} -``` - -## Chaining Operations - -You can chain multiple operations for complex workflows: - -```php -$result = Flowpipe::make() - ->send($rawData) - - // Pre-processing - ->validate(['required_field' => 'required']) - ->transform(fn($data) => array_map('trim', $data)) - - // Main processing with caching and retries - ->cache('processed-data', 1800) - ->retry(2, 500) - ->batch(25) - - // Rate limiting for external calls - ->rateLimit('api-calls', 100, 1) - - // Final processing - ->through([ - ProcessDataStep::class, - SaveToDbStep::class, - NotifyStep::class, - ]) - - ->thenReturn(); -``` - -## Performance Monitoring - -```php -use Grazulex\LaravelFlowpipe\Tracer\PerformanceTracer; - -$tracer = new PerformanceTracer(); - -$result = Flowpipe::make($tracer) - ->send($data) - ->through($complexSteps) - ->thenReturn(); - -// Check for performance issues -if ($tracer->hasPerformanceIssues()) { - $bottlenecks = $tracer->getBottlenecks(3); - $memoryHogs = $tracer->getMemoryHogs(3); - - // Log or alert about performance issues - Log::warning('Flowpipe performance issues detected', [ - 'bottlenecks' => $bottlenecks, - 'memory_hogs' => $memoryHogs, - 'total_time' => $tracer->getTotalExecutionTime(), - ]); -} -``` - -## Error Handling with Retry Logic - -```php -use Grazulex\LaravelFlowpipe\Steps\RetryStep; - -$result = Flowpipe::make() - ->send($data) - ->through([ - // Custom retry logic for different exceptions - RetryStep::make(5, 200, function ($exception, $attempt) { - // Retry network errors up to 3 times - if ($exception instanceof NetworkException && $attempt <= 3) { - return true; - } - - // Retry rate limit errors with exponential backoff - if ($exception instanceof RateLimitException) { - sleep(pow(2, $attempt)); // Exponential backoff - return $attempt <= 5; - } - - return false; - }), - - ApiCallStep::class, - ]) - ->thenReturn(); -``` - -These advanced features make Laravel Flowpipe a powerful tool for building robust, scalable, and maintainable data processing pipelines in your Laravel applications. diff --git a/docs/commands.md b/docs/commands.md deleted file mode 100644 index ccd028b..0000000 --- a/docs/commands.md +++ /dev/null @@ -1,512 +0,0 @@ -# Artisan Commands - -Laravel Flowpipe provides several Artisan commands to help you manage flow definitions and generate boilerplate code. - -## Available Commands - -### 1. `flowpipe:list` - -Lists all available flow definitions in your configured path. - -```bash -php artisan flowpipe:list -``` - -#### Options - -- `--detailed` : Show detailed information about each flow (steps, conditions, features) - -```bash -php artisan flowpipe:list --detailed -``` - -#### Example Output - -``` -Available Flow Definitions: - -user-registration - Description: Complete user registration flow - Steps: 5 - Conditions: 2 - Features: Email validation, Password hashing, Profile creation - -order-processing - Description: Process customer orders - Steps: 8 - Conditions: 3 - Features: Payment processing, Inventory check, Shipping calculation -``` - -### 2. `flowpipe:validate` - -Validates flow definition files to ensure they are correctly structured and all references are valid. - -```bash -php artisan flowpipe:validate -``` - -#### Options - -- `--path=` : Path to specific flow definition file to validate -- `--all` : Validate all flow definitions in the configured directory -- `--format=table` : Output format (table, json) - -#### Examples - -```bash -# Validate all flows with table output -php artisan flowpipe:validate --all - -# Validate specific flow -php artisan flowpipe:validate --path=user-registration.yaml - -# Get JSON output for programmatic processing -php artisan flowpipe:validate --all --format=json -``` - -#### Example Output - -**Table Format:** -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Flow โ”‚ Status โ”‚ Errors โ”‚ Warnings โ”‚ -โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค -โ”‚ UserProcessingFlow โ”‚ โœ… Valid โ”‚ 0 โ”‚ 0 โ”‚ -โ”‚ PaymentFlow โ”‚ โœ… Valid โ”‚ 0 โ”‚ 1 โ”‚ -โ”‚ InvalidFlow โ”‚ โŒ Invalid โ”‚ 3 โ”‚ 0 โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - -Errors in 'InvalidFlow': - - Step 1: Unsupported step type 'invalid_type' - - Step 2: Closure step missing 'action' field - - Step 3: Group 'non_existent_group' not found -``` - -**JSON Format:** -```json -{ - "valid": false, - "flows": [ - { - "name": "UserProcessingFlow", - "valid": true, - "errors": [], - "warnings": [], - "error_count": 0, - "warning_count": 0 - } - ], - "summary": { - "total": 3, - "valid": 2, - "invalid": 1, - "errors": 3, - "warnings": 1 - } -} -``` - -### 3. `flowpipe:run` - -Runs a flow definition from YAML with optional payload. - -```bash -php artisan flowpipe:run {flow} -``` - -#### Arguments - -- `flow` : The name of the flow to run - -#### Options - -- `--payload=` : Initial payload as JSON string - -#### Examples - -```bash -# Run a flow without payload -php artisan flowpipe:run user-registration - -# Run a flow with JSON payload -php artisan flowpipe:run user-registration --payload='{"name":"John","email":"john@example.com"}' -``` - -### 4. `flowpipe:make-flow` - -Creates a new flow definition file with a basic template. - -```bash -php artisan flowpipe:make-flow {name} -``` - -#### Arguments - -- `name` : The name of the flow definition file to create - -#### Options - -- `--template=basic` : Choose template type (basic, conditional, advanced) - -#### Examples - -```bash -# Create a basic flow -php artisan flowpipe:make-flow user-registration - -# Create a conditional flow -php artisan flowpipe:make-flow order-processing --template=conditional - -# Create an advanced flow -php artisan flowpipe:make-flow data-processing --template=advanced -``` - -#### Generated Templates - -**Basic Template:** -```yaml -flow: user-registration -description: User registration flow - -# Optional: Initial payload to send to the flow -# send: "initial data" - -steps: - # Example: Simple closure step - - type: closure - action: uppercase - - # Example: Custom step class - # - step: App\Flowpipe\Steps\YourCustomStep - - # Example: Another closure step - - type: closure - action: trim -``` - -**Conditional Template:** -```yaml -flow: order-processing -description: Order processing flow with conditions - -# Optional: Initial payload -# send: {"active": true, "name": "John Doe"} - -steps: - # Example: Conditional step with dot notation - - condition: user.is_active - then: - - type: closure - action: uppercase - # - step: App\Flowpipe\Steps\SendWelcomeEmailStep - else: - - type: closure - action: lowercase - # - step: App\Flowpipe\Steps\SendRejectionEmailStep - - # Example: Field-based condition - - condition: - field: status - operator: equals - value: "approved" - then: - - type: closure - action: trim -``` - -### 5. `flowpipe:make-step` - -Creates a new step class with proper structure and methods. - -```bash -php artisan flowpipe:make-step {name} -``` - -#### Arguments - -- `name` : The name of the step class to create - -#### Options - -- `--namespace=App\Flowpipe\Steps` : Custom namespace for the step class -- `--force` : Overwrite existing step class if it exists - -#### Examples - -```bash -# Create a basic step -php artisan flowpipe:make-step ValidateInputStep - -# Create a step with custom namespace -php artisan flowpipe:make-step PaymentStep --namespace=App\Services\Payment\Steps - -# Force overwrite existing step -php artisan flowpipe:make-step ValidateInputStep --force -``` - -#### Generated Step Class - -```php - Step1 - Step2[๐Ÿ”„ nested: process] - Step2:::nestedStyle - Step1 --> Step2 - End([๐Ÿ End]) - End:::startEndStyle - Step2 --> End -``` - -**Markdown Export:** -```markdown -# Flow Documentation: user-registration - -## ๐Ÿ“‹ Basic Information - -- **Flow Name**: `user-registration` -- **Type**: `flow` -- **Description**: User registration flow with validation and processing -- **Generated**: 2024-01-15 10:30:00 - -## ๐ŸŒŠ Flow Visualization - -```mermaid -flowchart TD - classDef groupStyle fill:#e1f5fe,stroke:#01579b,stroke-width:2px,color:#01579b - classDef nestedStyle fill:#f9fbe7,stroke:#33691e,stroke-width:2px,color:#33691e - classDef startEndStyle fill:#e8f5e8,stroke:#2e7d32,stroke-width:3px,color:#2e7d32 - - Start([๐Ÿš€ Start: Flow: user-registration]) - Start:::startEndStyle - Step1[๐Ÿ“ฆ Group: user-validation] - Step1:::groupStyle - Start --> Step1 - Step2[๐Ÿ”„ nested: process] - Step2:::nestedStyle - Step1 --> Step2 - End([๐Ÿ End]) - End:::startEndStyle - Step2 --> End -``` - -## ๐Ÿ”ง Detailed Steps - -### Step 1 - -**Type**: Group Step - -**Action**: `user-validation` - -### Step 2 - -**Type**: Nested Step - -**Action**: `process` -``` - -## Command Tips - -### 1. Batch Operations - -```bash -# List all flows and export them -php artisan flowpipe:list --detailed | grep -E "^[a-z-]+" | while read flow; do - php artisan flowpipe:export "$flow" md --output="docs/flows/$flow.md" -done -``` - -### 2. Development Workflow - -```bash -# Create a new flow -php artisan flowpipe:make-flow user-onboarding - -# Create required steps -php artisan flowpipe:make-step CreateUserStep -php artisan flowpipe:make-step SendWelcomeEmailStep -php artisan flowpipe:make-step SetupProfileStep - -# Create conditions -php artisan flowpipe:make-condition EmailValidCondition -php artisan flowpipe:make-condition ProfileCompleteCondition - -# Test the flow -php artisan flowpipe:list --detailed - -# Export documentation -php artisan flowpipe:export user-onboarding md -``` - -### 3. Documentation Generation - -```bash -# Generate documentation for all flows -mkdir -p docs/flows -php artisan flowpipe:list | tail -n +3 | while read flow; do - php artisan flowpipe:export "$flow" md --output="docs/flows/$flow.md" --include-steps -done -``` - -## Error Handling - -### Common Errors - -1. **Flow not found**: Check if the flow definition file exists -2. **Invalid format**: Ensure you're using supported formats (json, mermaid, md) -3. **Permission errors**: Check file permissions for output directories -4. **Class not found**: Ensure step and condition classes exist - -### Debug Mode - -Enable verbose output for troubleshooting: - -```bash -php artisan flowpipe:list -v -php artisan flowpipe:export user-registration json -vvv -``` - -## Extending Commands - -You can extend the existing commands or create new ones: - -```php - 'flow_definitions', - - /* - |-------------------------------------------------------------------------- - | Default Step Namespace - |-------------------------------------------------------------------------- - | - | This namespace will be prepended to step class names when resolving - | strings like 'MyStep' into 'App\\Flowpipe\\Steps\\MyStep'. - | - */ - 'step_namespace' => 'App\\Flowpipe\\Steps', - - /* - |-------------------------------------------------------------------------- - | Tracing Configuration - |-------------------------------------------------------------------------- - | - | This section configures the tracing functionality of Flowpipe. - | You can enable or disable tracing and set the default tracer class. - | - */ - 'tracing' => [ - 'enabled' => true, - 'default' => \Grazulex\LaravelFlowpipe\Tracer\BasicTracer::class, - ], - - /* - |-------------------------------------------------------------------------- - | Groups Configuration - |-------------------------------------------------------------------------- - | - | Configuration for step groups and nested flows. - | Groups can be defined in YAML and used across multiple flows. - | - */ - 'groups' => [ - 'enabled' => true, - 'auto_register' => true, // Automatically register groups from YAML files - 'definitions_path' => 'flow_definitions/groups', // Path to group definitions - ], -]; -``` - -## Configuration Details - -### Flow Definitions Path - -The `definitions_path` setting determines where your YAML flow definition files are stored: - -```php -'definitions_path' => 'flow_definitions', -``` - -This path is relative to your Laravel application's base path. You can change it to: -- `'resources/flows'` - Store flows in resources directory -- `'storage/flows'` - Store flows in storage directory -- `'config/flows'` - Store flows in config directory - -### Step Namespace - -The `step_namespace` setting defines the default namespace for your step classes: - -```php -'step_namespace' => 'App\\Flowpipe\\Steps', -``` - -When you reference a step in YAML as `MyStep`, it will be resolved to `App\Flowpipe\Steps\MyStep`. - -### Tracing Configuration - -Tracing helps you debug and monitor flow execution: - -```php -'tracing' => [ - 'enabled' => true, - 'default' => \Grazulex\LaravelFlowpipe\Tracer\BasicTracer::class, -], -``` - -- `enabled`: Whether tracing is active by default -- `default`: The default tracer class to use - -### Groups Configuration - -Groups configuration controls step groups and nested flows functionality: - -```php -'groups' => [ - 'enabled' => true, - 'auto_register' => true, // Automatically register groups from YAML files - 'definitions_path' => 'flow_definitions/groups', // Path to group definitions -], -``` - -- `enabled`: Whether groups functionality is active -- `auto_register`: Automatically load groups from YAML files on boot -- `definitions_path`: Path to group definition files (relative to base path) - -## Usage Examples - -### Basic Configuration - -Most users will only need to modify the paths and namespace: - -```php -// config/flowpipe.php -return [ - 'definitions_path' => 'resources/flows', - 'step_namespace' => 'App\\Services\\Flows\\Steps', - 'tracing' => [ - 'enabled' => true, - 'default' => \Grazulex\LaravelFlowpipe\Tracer\BasicTracer::class, - ], - 'groups' => [ - 'enabled' => true, - 'auto_register' => true, - 'definitions_path' => 'resources/flows/groups', - ], -]; -``` - -### Development vs Production - -For development, you might want tracing enabled: - -```php -// config/flowpipe.php (development) -return [ - 'definitions_path' => 'flow_definitions', - 'step_namespace' => 'App\\Flowpipe\\Steps', - 'tracing' => [ - 'enabled' => true, - 'default' => \Grazulex\LaravelFlowpipe\Tracer\BasicTracer::class, - ], - 'groups' => [ - 'enabled' => true, - 'auto_register' => true, - 'definitions_path' => 'flow_definitions/groups', - ], -]; -``` - -For production, you might disable tracing for performance: - -```php -// config/flowpipe.php (production) -return [ - 'definitions_path' => 'flow_definitions', - 'step_namespace' => 'App\\Flowpipe\\Steps', - 'tracing' => [ - 'enabled' => false, - 'default' => \Grazulex\LaravelFlowpipe\Tracer\BasicTracer::class, - ], - 'groups' => [ - 'enabled' => true, - 'auto_register' => true, - 'definitions_path' => 'flow_definitions/groups', - ], -]; -``` - -### Custom Tracer - -You can create and use your own tracer: - -```php -// config/flowpipe.php -return [ - 'definitions_path' => 'flow_definitions', - 'step_namespace' => 'App\\Flowpipe\\Steps', - 'tracing' => [ - 'enabled' => true, - 'default' => \App\Tracers\DatabaseTracer::class, - ], - 'groups' => [ - 'enabled' => true, - 'auto_register' => true, - 'definitions_path' => 'flow_definitions/groups', - ], -]; -``` - -## Available Tracers - -### BasicTracer - -The default tracer that provides basic tracing functionality: - -```php -'tracing' => [ - 'enabled' => true, - 'default' => \Grazulex\LaravelFlowpipe\Tracer\BasicTracer::class, -], -``` - -### TestTracer - -Useful for testing and debugging: - -```php -'tracing' => [ - 'enabled' => true, - 'default' => \Grazulex\LaravelFlowpipe\Tracer\TestTracer::class, -], -``` - -## Best Practices - -### 1. Organization - -Keep your flows organized by using descriptive paths: - -```php -'definitions_path' => 'resources/flows', -``` - -Then organize your files like: -``` -resources/flows/ -โ”œโ”€โ”€ user/ -โ”‚ โ”œโ”€โ”€ registration.yaml -โ”‚ โ””โ”€โ”€ activation.yaml -โ”œโ”€โ”€ order/ -โ”‚ โ”œโ”€โ”€ processing.yaml -โ”‚ โ””โ”€โ”€ fulfillment.yaml -โ””โ”€โ”€ admin/ - โ””โ”€โ”€ maintenance.yaml -``` - -### 2. Namespace Convention - -Use a consistent namespace structure: - -```php -'step_namespace' => 'App\\Flowpipe\\Steps', -``` - -And organize your step classes: -``` -app/Flowpipe/Steps/ -โ”œโ”€โ”€ User/ -โ”‚ โ”œโ”€โ”€ RegistrationStep.php -โ”‚ โ””โ”€โ”€ ActivationStep.php -โ”œโ”€โ”€ Order/ -โ”‚ โ”œโ”€โ”€ ProcessingStep.php -โ”‚ โ””โ”€โ”€ FulfillmentStep.php -โ””โ”€โ”€ Admin/ - โ””โ”€โ”€ MaintenanceStep.php -``` - -### 3. Tracing Strategy - -Enable tracing during development and testing: - -```php -'tracing' => [ - 'enabled' => app()->environment('local', 'testing'), - 'default' => \Grazulex\LaravelFlowpipe\Tracer\BasicTracer::class, -], -``` - -## Configuration per Environment - -You can create environment-specific configurations: - -### config/flowpipe.php (base configuration) -```php -return [ - 'definitions_path' => 'flow_definitions', - 'step_namespace' => 'App\\Flowpipe\\Steps', - 'tracing' => [ - 'enabled' => true, - 'default' => \Grazulex\LaravelFlowpipe\Tracer\BasicTracer::class, - ], - 'groups' => [ - 'enabled' => true, - 'auto_register' => true, - 'definitions_path' => 'flow_definitions/groups', - ], -]; -``` - -### Programmatic Configuration - -You can also configure Flowpipe programmatically: - -```php -// In a service provider -use Grazulex\LaravelFlowpipe\Flowpipe; - -public function boot() -{ - // Override configuration at runtime - config(['flowpipe.tracing.enabled' => false]); - - // Or configure specific settings - if (app()->environment('production')) { - config(['flowpipe.tracing.enabled' => false]); - } -} -``` - -## Troubleshooting - -### Common Issues - -1. **Flow definitions not found**: Check that your `definitions_path` is correct and files exist -2. **Step class not found**: Verify your `step_namespace` matches your actual class structure -3. **Tracing not working**: Ensure tracing is enabled and the tracer class exists -4. **Groups not loading**: Check that groups are enabled and the `groups.definitions_path` is correct - -### Common Solutions - -#### Flow Not Found Error -```bash -# Check if your flow files exist -ls -la flow_definitions/ -``` - -#### Step Class Resolution Error -```php -// Make sure your step class exists at the correct path -// If step_namespace is 'App\\Flowpipe\\Steps' -// and your YAML references 'MyStep' -// The class should be at: app/Flowpipe/Steps/MyStep.php -``` - -#### Tracing Issues -```php -// Check if tracer class exists -use Grazulex\LaravelFlowpipe\Tracer\BasicTracer; -$tracer = new BasicTracer(); -``` - -### Debug Tips - -1. **Check configuration loading**: - ```php - dd(config('flowpipe')); - ``` - -2. **Verify file paths**: - ```php - dd(base_path(config('flowpipe.definitions_path'))); - ``` - -3. **Test step resolution**: - ```php - use Grazulex\LaravelFlowpipe\Support\StepResolver; - $resolver = new StepResolver(); - dd($resolver->resolve('MyStep')); - ``` - -## Summary - -The current Laravel Flowpipe configuration is minimal and focused. It provides: - -- **Simple path configuration** for flow definitions -- **Namespace configuration** for step classes -- **Basic tracing configuration** for debugging -- **Groups configuration** for step groups and nested flows - -This keeps the configuration clean and focused on essential features while remaining easily extensible for future enhancements. diff --git a/docs/error-handling-usage.md b/docs/error-handling-usage.md deleted file mode 100644 index fc900cc..0000000 --- a/docs/error-handling-usage.md +++ /dev/null @@ -1,407 +0,0 @@ -# Error Handling Usage Guide - -This guide provides practical examples and patterns for implementing error handling in Laravel Flowpipe applications. - -## Quick Start - -### Basic Error Handling - -```php -use Grazulex\LaravelFlowpipe\Flowpipe; - -// Simple retry with exponential backoff -$result = Flowpipe::make() - ->send($data) - ->exponentialBackoff(3, 100, 2.0) - ->through([ - fn($data, $next) => $next(unreliableOperation($data)), - ]) - ->thenReturn(); - -// Fallback to default value -$result = Flowpipe::make() - ->send($data) - ->withFallback(fn($payload, $error) => ['status' => 'cached']) - ->through([ - fn($data, $next) => $next(externalApiCall($data)), - ]) - ->thenReturn(); -``` - -## Common Patterns - -### 1. Web API Integration - -```php -use Grazulex\LaravelFlowpipe\ErrorHandling\Strategies\CompositeStrategy; -use Grazulex\LaravelFlowpipe\ErrorHandling\Strategies\RetryStrategy; -use Grazulex\LaravelFlowpipe\ErrorHandling\Strategies\FallbackStrategy; - -// API with retry and fallback -$apiStrategy = CompositeStrategy::make() - ->retry(RetryStrategy::exponentialBackoff(3, 200, 2.0)) - ->fallback(FallbackStrategy::make(function ($payload, $error) { - // Use cached data if API fails - return Cache::get('api_data_' . $payload['key'], ['status' => 'cached']); - })); - -$result = Flowpipe::make() - ->send(['key' => 'user_123', 'endpoint' => '/api/users/123']) - ->withErrorHandler($apiStrategy) - ->through([ - fn($data, $next) => $next(Http::get($data['endpoint'])->json()), - ]) - ->thenReturn(); -``` - -### 2. Database Operations - -```php -use Grazulex\LaravelFlowpipe\ErrorHandling\Strategies\CompensationStrategy; -use Illuminate\Support\Facades\DB; - -// Database transaction with rollback -$result = Flowpipe::make() - ->send($transactionData) - ->withCompensation(function ($payload, $error, $context) { - // Rollback transaction on any failure - DB::rollBack(); - Log::error('Transaction failed, rolled back', [ - 'error' => $error->getMessage(), - 'payload' => $payload, - ]); - return array_merge($payload, ['rolled_back' => true]); - }) - ->through([ - fn($data, $next) => $next(DB::transaction(fn() => createComplexData($data))), - ]) - ->thenReturn(); -``` - -### 3. File Processing - -```php -use Grazulex\LaravelFlowpipe\ErrorHandling\Strategies\RetryStrategy; -use Grazulex\LaravelFlowpipe\ErrorHandling\Strategies\CompensationStrategy; - -// File processing with retry and cleanup -$result = Flowpipe::make() - ->send($fileData) - - // Retry file processing - ->withRetryStrategy(RetryStrategy::exponentialBackoff(3, 500, 2.0)) - ->through([ - fn($data, $next) => $next(processLargeFile($data)), - ]) - - // Cleanup temporary files on failure - ->withCompensation(function ($payload, $error, $context) { - if (isset($payload['temp_files'])) { - foreach ($payload['temp_files'] as $file) { - @unlink($file); - } - } - return array_merge($payload, ['cleanup_performed' => true]); - }) - ->through([ - fn($data, $next) => $next(finalizeFile($data)), - ]) - - ->thenReturn(); -``` - -### 4. Payment Processing - -```php -use Grazulex\LaravelFlowpipe\ErrorHandling\Strategies\CompositeStrategy; -use Grazulex\LaravelFlowpipe\ErrorHandling\Strategies\RetryStrategy; -use Grazulex\LaravelFlowpipe\ErrorHandling\Strategies\CompensationStrategy; - -// Payment with retry and refund compensation -$paymentStrategy = CompositeStrategy::make() - ->retry(RetryStrategy::exponentialBackoff(3, 300, 2.0)) - ->compensate(CompensationStrategy::make(function ($payload, $error, $context) { - // Refund payment if subsequent steps fail - if (isset($payload['payment_id'])) { - PaymentService::refund($payload['payment_id']); - } - return array_merge($payload, ['refunded' => true]); - })); - -$result = Flowpipe::make() - ->send($paymentData) - ->withErrorHandler($paymentStrategy) - ->through([ - fn($data, $next) => $next(chargePayment($data)), - fn($data, $next) => $next(createOrder($data)), - fn($data, $next) => $next(updateInventory($data)), - ]) - ->thenReturn(); -``` - -## Advanced Patterns - -### Exception-Specific Handling - -```php -use App\Exceptions\{NetworkException, ValidationException, PaymentException}; - -$result = Flowpipe::make() - ->send($data) - - // Handle network errors with retry - ->withRetryStrategy(RetryStrategy::forException(NetworkException::class, 5, 100)) - - // Handle validation errors with fallback - ->fallbackOnException(ValidationException::class, function ($payload, $error) { - return array_merge($payload, ['validation_errors' => $error->getErrors()]); - }) - - // Handle payment errors with compensation - ->compensateOnException(PaymentException::class, function ($payload, $error) { - // Specific compensation for payment failures - return processPaymentFailure($payload, $error); - }) - - ->through($steps) - ->thenReturn(); -``` - -### Custom Retry Logic - -```php -use Grazulex\LaravelFlowpipe\ErrorHandling\Strategies\RetryStrategy; - -$customRetry = RetryStrategy::make(5, 100, function ($exception, $attempt) { - // Custom retry logic - if ($exception instanceof RateLimitException) { - // Use delay from rate limit header - $delay = $exception->getRetryAfter() * 1000; - usleep($delay); - return $attempt <= 3; - } - - if ($exception instanceof NetworkException) { - // Retry network errors with exponential backoff - $delay = 100 * pow(2, $attempt - 1); - usleep($delay * 1000); - return true; - } - - // Don't retry validation errors - return !($exception instanceof ValidationException); -}); - -$result = Flowpipe::make() - ->send($data) - ->withRetryStrategy($customRetry) - ->through($steps) - ->thenReturn(); -``` - -### Circuit Breaker Pattern - -```php -use Grazulex\LaravelFlowpipe\ErrorHandling\Strategies\CompositeStrategy; -use Illuminate\Support\Facades\Cache; - -class CircuitBreakerStrategy extends CompositeStrategy -{ - private string $serviceName; - private int $failureThreshold; - private int $timeoutSeconds; - - public function __construct(string $serviceName, int $failureThreshold = 5, int $timeoutSeconds = 60) - { - $this->serviceName = $serviceName; - $this->failureThreshold = $failureThreshold; - $this->timeoutSeconds = $timeoutSeconds; - - parent::__construct(); - } - - public function handle(Throwable $error, mixed $payload, int $attemptNumber, array $context = []): ErrorHandlerResult - { - $key = "circuit_breaker_{$this->serviceName}"; - $failures = Cache::get($key, 0); - - if ($failures >= $this->failureThreshold) { - // Circuit is open, use fallback immediately - return ErrorHandlerResult::fallback( - $this->getFallbackData($payload), - array_merge($context, ['circuit_breaker' => 'open']) - ); - } - - // Try normal error handling - $result = parent::handle($error, $payload, $attemptNumber, $context); - - if ($result->action === ErrorHandlerAction::FAIL) { - // Increment failure count - Cache::put($key, $failures + 1, $this->timeoutSeconds); - } else { - // Reset failure count on success - Cache::forget($key); - } - - return $result; - } - - private function getFallbackData(mixed $payload): mixed - { - // Return cached data or default response - return Cache::get("fallback_{$this->serviceName}", ['status' => 'degraded']); - } -} - -// Usage -$circuitBreaker = new CircuitBreakerStrategy('external_api', 5, 300); -$result = Flowpipe::make() - ->send($data) - ->withErrorHandler($circuitBreaker) - ->through($steps) - ->thenReturn(); -``` - -## Monitoring and Observability - -### Logging Error Handling - -```php -use Illuminate\Support\Facades\Log; - -$strategy = FallbackStrategy::make(function ($payload, $error) { - // Structured logging - Log::error('Fallback strategy activated', [ - 'error_type' => get_class($error), - 'error_message' => $error->getMessage(), - 'payload_type' => is_object($payload) ? get_class($payload) : gettype($payload), - 'timestamp' => now()->toISOString(), - 'correlation_id' => request()->header('X-Correlation-ID'), - ]); - - return getFallbackData($payload); -}); -``` - -### Metrics Collection - -```php -use Grazulex\LaravelFlowpipe\ErrorHandling\Strategies\CompositeStrategy; - -class MetricsAwareCompositeStrategy extends CompositeStrategy -{ - public function handle(Throwable $error, mixed $payload, int $attemptNumber, array $context = []): ErrorHandlerResult - { - $startTime = microtime(true); - - // Collect metrics - app('metrics')->increment('flowpipe.error_handling.attempts', [ - 'error_type' => get_class($error), - 'attempt_number' => $attemptNumber, - ]); - - $result = parent::handle($error, $payload, $attemptNumber, $context); - - $duration = microtime(true) - $startTime; - app('metrics')->timing('flowpipe.error_handling.duration', $duration * 1000, [ - 'action' => $result->action->value, - 'error_type' => get_class($error), - ]); - - return $result; - } -} -``` - -## Testing Error Handling - -### Unit Testing - -```php -use Grazulex\LaravelFlowpipe\Flowpipe; -use Grazulex\LaravelFlowpipe\ErrorHandling\Strategies\RetryStrategy; - -class ErrorHandlingTest extends TestCase -{ - public function test_retry_strategy_works() - { - $attempts = 0; - $strategy = RetryStrategy::make(3, 0); - - $result = Flowpipe::make() - ->send('test') - ->withRetryStrategy($strategy) - ->through([ - function ($data, $next) use (&$attempts) { - $attempts++; - if ($attempts < 3) { - throw new RuntimeException('Simulated failure'); - } - return $next('success'); - }, - ]) - ->thenReturn(); - - $this->assertEquals('success', $result); - $this->assertEquals(3, $attempts); - } - - public function test_fallback_strategy_works() - { - $result = Flowpipe::make() - ->send('test') - ->withFallback(fn($payload, $error) => 'fallback_result') - ->through([ - fn($data, $next) => throw new RuntimeException('Always fails'), - ]) - ->thenReturn(); - - $this->assertEquals('fallback_result', $result); - } -} -``` - -### Integration Testing - -```php -use Grazulex\LaravelFlowpipe\Flowpipe; -use Illuminate\Support\Facades\Http; - -class APIIntegrationTest extends TestCase -{ - public function test_api_error_handling() - { - // Mock external API failure - Http::fake([ - 'api.example.com/*' => Http::response(null, 500), - ]); - - $result = Flowpipe::make() - ->send(['user_id' => 123]) - ->exponentialBackoff(3, 100, 2.0) - ->withFallback(fn($payload, $error) => ['cached' => true]) - ->through([ - fn($data, $next) => $next(Http::get("https://api.example.com/users/{$data['user_id']}")->json()), - ]) - ->thenReturn(); - - $this->assertTrue($result['cached']); - } -} -``` - -## Best Practices - -1. **Strategy Selection**: Choose appropriate strategies for each error type -2. **Monitoring**: Always log error handling activities -3. **Testing**: Test all error paths thoroughly -4. **Performance**: Monitor error handling overhead -5. **Documentation**: Document error handling strategies for your team -6. **Graceful Degradation**: Provide meaningful fallback behavior -7. **Resource Management**: Clean up resources in compensation strategies -8. **Circuit Breakers**: Implement circuit breakers for external dependencies -9. **Correlation IDs**: Use correlation IDs for tracking across services -10. **Metrics**: Collect metrics on error rates and handling effectiveness - -This guide provides a foundation for implementing robust error handling in your Laravel Flowpipe applications. Adapt these patterns to your specific use cases and requirements. \ No newline at end of file diff --git a/docs/error-handling.md b/docs/error-handling.md deleted file mode 100644 index cebec96..0000000 --- a/docs/error-handling.md +++ /dev/null @@ -1,709 +0,0 @@ -# Error Handling Strategies - -Laravel Flowpipe provides sophisticated error handling capabilities through customizable strategies. This allows you to handle different types of errors in different ways: retry transient failures, fallback to alternative solutions, or compensate for failed operations. - -## Overview - -The error handling system is built around the concept of strategies that can be combined to create robust error handling patterns: - -- **Retry Strategy**: Automatically retry failed operations with configurable delays -- **Fallback Strategy**: Provide alternative results when operations fail -- **Compensation Strategy**: Execute rollback or cleanup operations -- **Composite Strategy**: Combine multiple strategies for comprehensive error handling - -## Basic Usage - -### Retry Strategy - -```php -use Grazulex\LaravelFlowpipe\Flowpipe; - -// Simple retry with exponential backoff -$result = Flowpipe::make() - ->send($data) - ->exponentialBackoff(3, 100, 2.0) // 3 attempts, 100ms base delay, 2x multiplier - ->through([ - fn($data, $next) => $next(unreliableApiCall($data)), - ]) - ->thenReturn(); - -// Linear backoff -$result = Flowpipe::make() - ->send($data) - ->linearBackoff(3, 100, 50) // 3 attempts, 100ms base, 50ms increment - ->through([ - fn($data, $next) => $next(unreliableApiCall($data)), - ]) - ->thenReturn(); -``` - -### Fallback Strategy - -```php -// Simple fallback with default value -$result = Flowpipe::make() - ->send($data) - ->withFallback(fn($payload, $error) => ['status' => 'cached', 'data' => $cachedData]) - ->through([ - fn($data, $next) => $next(fetchFromApi($data)), - ]) - ->thenReturn(); - -// Fallback for specific exceptions -$result = Flowpipe::make() - ->send($data) - ->fallbackOnException(NetworkException::class, fn($payload, $error) => getCachedData($payload)) - ->through([ - fn($data, $next) => $next(fetchFromApi($data)), - ]) - ->thenReturn(); -``` - -### Compensation Strategy - -```php -// Compensation (rollback) on failure -$result = Flowpipe::make() - ->send($orderData) - ->withCompensation(function ($payload, $error, $context) { - // Rollback order creation - Order::where('id', $payload['order_id'])->delete(); - return array_merge($payload, ['compensated' => true]); - }) - ->through([ - fn($data, $next) => $next(processOrder($data)), - ]) - ->thenReturn(); - -// Compensation for specific exceptions -$result = Flowpipe::make() - ->send($data) - ->compensateOnException(PaymentException::class, fn($payload, $error) => refundPayment($payload)) - ->through([ - fn($data, $next) => $next(processPayment($data)), - ]) - ->thenReturn(); -``` - -## Advanced Usage - -### Composite Strategy - -Combine multiple strategies to create comprehensive error handling: - -```php -use Grazulex\LaravelFlowpipe\ErrorHandling\Strategies\CompositeStrategy; -use Grazulex\LaravelFlowpipe\ErrorHandling\Strategies\RetryStrategy; -use Grazulex\LaravelFlowpipe\ErrorHandling\Strategies\FallbackStrategy; - -$compositeStrategy = CompositeStrategy::make() - ->retry(RetryStrategy::exponentialBackoff(3, 100, 2.0)) - ->fallback(FallbackStrategy::withDefault(['status' => 'cached'])); - -$result = Flowpipe::make() - ->send($data) - ->withErrorHandler($compositeStrategy) - ->through([ - fn($data, $next) => $next(unreliableApiCall($data)), - ]) - ->thenReturn(); -``` - -### Custom Retry Logic - -```php -use Grazulex\LaravelFlowpipe\ErrorHandling\Strategies\RetryStrategy; - -// Custom retry condition -$strategy = RetryStrategy::make(5, 100, function ($exception, $attempt) { - // Only retry network errors - if ($exception instanceof NetworkException) { - return true; - } - - // Retry rate limit errors with exponential backoff - if ($exception instanceof RateLimitException) { - sleep(pow(2, $attempt)); // Custom backoff - return $attempt <= 3; - } - - return false; -}); - -$result = Flowpipe::make() - ->send($data) - ->withRetryStrategy($strategy) - ->through([ - fn($data, $next) => $next(apiCall($data)), - ]) - ->thenReturn(); -``` - -### Exception-Specific Handling - -```php -// Handle different exceptions differently -$result = Flowpipe::make() - ->send($data) - ->fallbackOnException(ValidationException::class, function ($payload, $error) { - return array_merge($payload, ['validation_errors' => $error->getErrors()]); - }) - ->compensateOnException(DatabaseException::class, function ($payload, $error) { - // Rollback database changes - DB::rollBack(); - return array_merge($payload, ['database_rolled_back' => true]); - }) - ->through([ - fn($data, $next) => $next(processData($data)), - ]) - ->thenReturn(); -``` - -## Error Handling Strategies - -### RetryStrategy - -Automatically retries failed operations with configurable delays and conditions. - -```php -use Grazulex\LaravelFlowpipe\ErrorHandling\Strategies\RetryStrategy; - -// Basic retry -$strategy = RetryStrategy::make(3, 100); // 3 attempts, 100ms delay - -// Exponential backoff -$strategy = RetryStrategy::exponentialBackoff(3, 100, 2.0); - -// Linear backoff -$strategy = RetryStrategy::linearBackoff(3, 100, 50); - -// Exception-specific retry -$strategy = RetryStrategy::forException(NetworkException::class, 5, 200); -``` - -### FallbackStrategy - -Provides alternative results when operations fail. - -```php -use Grazulex\LaravelFlowpipe\ErrorHandling\Strategies\FallbackStrategy; - -// Default value fallback -$strategy = FallbackStrategy::withDefault(['status' => 'error']); - -// Transform fallback -$strategy = FallbackStrategy::withTransform(fn($payload, $error) => [ - 'original' => $payload, - 'error' => $error->getMessage(), - 'fallback' => true -]); - -// Custom fallback handler -$strategy = FallbackStrategy::make(function ($payload, $error) { - // Custom fallback logic - return getCachedData($payload); -}); - -// Exception-specific fallback -$strategy = FallbackStrategy::forException(NetworkException::class, fn() => getCachedData()); -``` - -### CompensationStrategy - -Executes rollback or cleanup operations when failures occur. - -```php -use Grazulex\LaravelFlowpipe\ErrorHandling\Strategies\CompensationStrategy; - -// Basic compensation -$strategy = CompensationStrategy::make(function ($payload, $error, $context) { - // Rollback logic - rollbackTransaction($payload); - return array_merge($payload, ['compensated' => true]); -}); - -// Rollback helper -$strategy = CompensationStrategy::rollback(function ($payload, $error) { - DB::rollBack(); - return $payload; -}); - -// Cleanup helper -$strategy = CompensationStrategy::cleanup(function ($payload, $error) { - cleanupResources($payload); - return $payload; -}); -``` - -## Real-World Examples - -### E-commerce Order Processing - -```php -$result = Flowpipe::make() - ->send($orderData) - - // Retry payment processing - ->exponentialBackoff(3, 200, 2.0) - ->through([ - fn($data, $next) => $next(processPayment($data)), - ]) - - // Fallback to alternative payment method - ->withFallback(function ($payload, $error) { - return processAlternativePayment($payload); - }) - ->through([ - fn($data, $next) => $next(createOrder($data)), - ]) - - // Compensate by refunding if fulfillment fails - ->withCompensation(function ($payload, $error) { - refundPayment($payload['payment_id']); - return array_merge($payload, ['refunded' => true]); - }) - ->through([ - fn($data, $next) => $next(fulfillOrder($data)), - ]) - - ->thenReturn(); -``` - -## Real-World Examples - -### E-commerce Order Processing - -```php -$result = Flowpipe::make() - ->send($orderData) - - // Retry payment processing - ->exponentialBackoff(3, 200, 2.0) - ->through([ - fn($data, $next) => $next(processPayment($data)), - ]) - - // Fallback to alternative payment method - ->withFallback(function ($payload, $error) { - return processAlternativePayment($payload); - }) - ->through([ - fn($data, $next) => $next(createOrder($data)), - ]) - - // Compensate by refunding if fulfillment fails - ->withCompensation(function ($payload, $error) { - refundPayment($payload['payment_id']); - return array_merge($payload, ['refunded' => true]); - }) - ->through([ - fn($data, $next) => $next(fulfillOrder($data)), - ]) - - ->thenReturn(); -``` - -### Microservices Integration with Circuit Breaker - -```php -use Grazulex\LaravelFlowpipe\ErrorHandling\Strategies\CompositeStrategy; -use Grazulex\LaravelFlowpipe\ErrorHandling\Strategies\RetryStrategy; -use Grazulex\LaravelFlowpipe\ErrorHandling\Strategies\FallbackStrategy; - -$circuitBreakerStrategy = CompositeStrategy::make() - ->retry(RetryStrategy::exponentialBackoff(3, 100, 2.0)) - ->fallback(FallbackStrategy::make(function ($payload, $error) { - // Circuit breaker - use cached data - return getCachedData($payload['service_key']); - })); - -$result = Flowpipe::make() - ->send(['service_key' => 'user_service', 'user_id' => 123]) - ->withErrorHandler($circuitBreakerStrategy) - ->through([ - fn($data, $next) => $next(callUserMicroservice($data)), - ]) - ->thenReturn(); -``` - -### Database Transaction with Saga Pattern - -```php -use Grazulex\LaravelFlowpipe\ErrorHandling\Strategies\CompensationStrategy; - -$sagaStrategy = CompensationStrategy::make(function ($payload, $error, $context) { - // Compensate by rolling back all operations - foreach ($payload['operations'] as $operation) { - $operation['rollback'](); - } - return array_merge($payload, ['saga_compensated' => true]); -}); - -$result = Flowpipe::make() - ->send($transactionData) - ->withErrorHandler($sagaStrategy) - ->through([ - fn($data, $next) => $next(step1Transaction($data)), - fn($data, $next) => $next(step2Transaction($data)), - fn($data, $next) => $next(step3Transaction($data)), - ]) - ->thenReturn(); -``` - -### File Processing with Cleanup - -```php -$result = Flowpipe::make() - ->send($fileData) - - // Retry file processing - ->exponentialBackoff(3, 500, 2.0) - ->through([ - fn($data, $next) => $next(processLargeFile($data)), - ]) - - // Compensate by cleaning up temporary files - ->withCompensation(function ($payload, $error, $context) { - cleanupTemporaryFiles($payload['temp_files']); - return array_merge($payload, ['cleanup_performed' => true]); - }) - ->through([ - fn($data, $next) => $next(finalizeProcessing($data)), - ]) - - ->thenReturn(); -``` - -### API Rate Limiting with Backoff - -```php -use Grazulex\LaravelFlowpipe\ErrorHandling\Strategies\RetryStrategy; - -// Custom retry strategy for rate limiting -$rateLimitStrategy = RetryStrategy::make(5, 100, function ($exception, $attempt) { - if ($exception instanceof RateLimitException) { - // Extract retry-after header or use exponential backoff - $delay = $exception->getRetryAfter() ?? (100 * pow(2, $attempt - 1)); - usleep($delay * 1000); - return true; - } - return false; -}); - -$result = Flowpipe::make() - ->send($apiRequest) - ->withRetryStrategy($rateLimitStrategy) - ->through([ - fn($data, $next) => $next(makeAPIRequest($data)), - ]) - ->thenReturn(); -``` - -### Multi-Step User Registration - -```php -$result = Flowpipe::make() - ->send($userData) - - // Retry user creation - ->exponentialBackoff(3, 100, 2.0) - ->through([ - fn($data, $next) => $next(createUser($data)), - ]) - - // Fallback to guest user if profile creation fails - ->withFallback(function ($payload, $error) { - return array_merge($payload, ['profile_type' => 'guest']); - }) - ->through([ - fn($data, $next) => $next(createUserProfile($data)), - ]) - - // Compensate by deleting user if email sending fails - ->withCompensation(function ($payload, $error) { - deleteUser($payload['user_id']); - return array_merge($payload, ['user_deleted' => true]); - }) - ->through([ - fn($data, $next) => $next(sendWelcomeEmail($data)), - ]) - - ->thenReturn(); -``` - -### User Registration with Multiple Services - -```php -$result = Flowpipe::make() - ->send($userData) - - // Retry user creation - ->exponentialBackoff(3, 100, 2.0) - ->through([ - fn($data, $next) => $next(createUser($data)), - ]) - - // Fallback to guest user if profile creation fails - ->withFallback(function ($payload, $error) { - return array_merge($payload, ['profile_type' => 'guest']); - }) - ->through([ - fn($data, $next) => $next(createUserProfile($data)), - ]) - - // Compensate by deleting user if email sending fails - ->withCompensation(function ($payload, $error) { - deleteUser($payload['user_id']); - return array_merge($payload, ['user_deleted' => true]); - }) - ->through([ - fn($data, $next) => $next(sendWelcomeEmail($data)), - ]) - - ->thenReturn(); -``` - -## Error Context - -All error handling strategies provide rich context about the error: - -```php -$strategy = FallbackStrategy::make(function ($payload, $error) { - // Error context is available - logger()->error('Fallback triggered', [ - 'error' => $error->getMessage(), - 'payload' => $payload, - 'timestamp' => now(), - ]); - - return getFallbackData($payload); -}); -``` - -## Best Practices - -1. **Use Appropriate Strategies**: Choose the right strategy for each type of error -2. **Combine Strategies**: Use composite strategies for comprehensive error handling -3. **Log Errors**: Always log errors for debugging and monitoring -4. **Test Error Paths**: Test all error handling paths in your application -5. **Set Reasonable Limits**: Don't retry indefinitely - set appropriate limits -6. **Monitor Performance**: Error handling can impact performance - monitor accordingly - -## Configuration - -You can configure error handling globally in your application's service container: - -```php -// In a service provider -$this->app->bind('flowpipe.error_handler', function () { - return CompositeStrategy::make() - ->retry(RetryStrategy::exponentialBackoff(3, 100, 2.0)) - ->fallback(FallbackStrategy::withDefault(['status' => 'error'])); -}); -``` - -Then use it in your flows: - -```php -$result = Flowpipe::make() - ->send($data) - ->withErrorHandler(app('flowpipe.error_handler')) - ->through($steps) - ->thenReturn(); -``` - -This comprehensive error handling system makes your workflows more robust and reliable by providing multiple layers of error recovery and handling strategies. - -## Monitoring and Debugging - -### Error Context and Logging - -All error handling strategies provide rich context for monitoring and debugging: - -```php -use Grazulex\LaravelFlowpipe\ErrorHandling\Strategies\FallbackStrategy; -use Illuminate\Support\Facades\Log; - -$strategy = FallbackStrategy::make(function ($payload, $error) { - // Log error with context - Log::error('Fallback strategy activated', [ - 'error_message' => $error->getMessage(), - 'error_type' => get_class($error), - 'payload' => $payload, - 'timestamp' => now()->toISOString(), - 'trace' => $error->getTraceAsString(), - ]); - - return getFallbackData($payload); -}); -``` - -### Retry Monitoring - -```php -use Grazulex\LaravelFlowpipe\ErrorHandling\Strategies\RetryStrategy; - -$retryStrategy = RetryStrategy::exponentialBackoff(3, 100, 2.0, function ($exception, $attempt) { - // Log retry attempts - Log::warning("Retry attempt {$attempt} for {$exception->getMessage()}"); - - // Only retry specific exceptions - return $exception instanceof TransientException; -}); -``` - -### Compensation Tracking - -```php -use Grazulex\LaravelFlowpipe\ErrorHandling\Strategies\CompensationStrategy; - -$compensationStrategy = CompensationStrategy::make(function ($payload, $error, $context) { - // Log compensation activity - Log::critical('Compensation strategy activated', [ - 'original_error' => $error->getMessage(), - 'compensation_context' => $context, - 'payload' => $payload, - 'timestamp' => now()->toISOString(), - ]); - - // Perform compensation - performRollback($payload); - - // Return enriched payload - return array_merge($payload, [ - 'compensation_timestamp' => now()->toISOString(), - 'compensation_reason' => $error->getMessage(), - ]); -}); -``` - -### Performance Monitoring - -```php -use Grazulex\LaravelFlowpipe\Flowpipe; -use Grazulex\LaravelFlowpipe\Tracer\PerformanceTracer; - -// Use performance tracer to monitor error handling overhead -$result = Flowpipe::performance() - ->send($data) - ->exponentialBackoff(3, 100, 2.0) - ->through([ - fn($data, $next) => $next(expensiveOperation($data)), - ]) - ->thenReturn(); - -// Access performance metrics -$tracer = Flowpipe::performance()->context()->tracer(); -$metrics = $tracer->all(); -``` - -### Integration with Monitoring Systems - -```php -use Grazulex\LaravelFlowpipe\ErrorHandling\Strategies\CompositeStrategy; -use Grazulex\LaravelFlowpipe\ErrorHandling\Strategies\RetryStrategy; -use Grazulex\LaravelFlowpipe\ErrorHandling\Strategies\FallbackStrategy; - -// Custom monitoring wrapper -class MonitoredCompositeStrategy extends CompositeStrategy -{ - public function handle(Throwable $error, mixed $payload, int $attemptNumber, array $context = []): ErrorHandlerResult - { - // Send metrics to monitoring system - app('monitoring')->increment('flowpipe.error_handling.attempts', [ - 'error_type' => get_class($error), - 'attempt_number' => $attemptNumber, - ]); - - $startTime = microtime(true); - $result = parent::handle($error, $payload, $attemptNumber, $context); - $duration = microtime(true) - $startTime; - - // Track error handling performance - app('monitoring')->timing('flowpipe.error_handling.duration', $duration * 1000, [ - 'action' => $result->action->value, - 'error_type' => get_class($error), - ]); - - return $result; - } -} -``` - -## Best Practices - -1. **Choose Appropriate Strategies**: Select the right strategy for each error type -2. **Combine Strategies Wisely**: Use composite strategies for comprehensive coverage -3. **Set Reasonable Limits**: Don't retry indefinitely - balance resilience with performance -4. **Monitor Error Patterns**: Track error rates and patterns to identify system issues -5. **Test Error Paths**: Ensure all error handling paths are tested -6. **Use Structured Logging**: Log errors with consistent structure for better analysis -7. **Consider Circuit Breakers**: Implement circuit breaker patterns for external dependencies -8. **Resource Cleanup**: Always ensure resources are properly cleaned up in compensation strategies -9. **Graceful Degradation**: Provide meaningful fallback behavior -10. **Documentation**: Document your error handling strategies for team understanding - -## Configuration and Customization - -### Global Error Handler Configuration - -```php -// In a service provider -$this->app->bind('flowpipe.default_error_handler', function () { - return CompositeStrategy::make() - ->retry(RetryStrategy::exponentialBackoff(3, 100, 2.0)) - ->fallback(FallbackStrategy::withDefault(['status' => 'error', 'fallback' => true])); -}); - -// Usage -$result = Flowpipe::make() - ->send($data) - ->withErrorHandler(app('flowpipe.default_error_handler')) - ->through($steps) - ->thenReturn(); -``` - -### Custom Error Handler Factory - -```php -class ErrorHandlerFactory -{ - public static function forExternalAPI(): CompositeStrategy - { - return CompositeStrategy::make() - ->retry(RetryStrategy::exponentialBackoff(3, 200, 2.0)) - ->fallback(FallbackStrategy::make(fn($payload, $error) => getCachedData($payload))); - } - - public static function forDatabase(): CompositeStrategy - { - return CompositeStrategy::make() - ->retry(RetryStrategy::linearBackoff(5, 50, 25)) - ->compensate(CompensationStrategy::rollback(fn($payload, $error) => DB::rollBack())); - } - - public static function forPayment(): CompositeStrategy - { - return CompositeStrategy::make() - ->retry(RetryStrategy::exponentialBackoff(3, 300, 2.0)) - ->compensate(CompensationStrategy::make(fn($payload, $error) => refundPayment($payload))); - } -} -``` - -### Environment-Specific Configuration - -```php -// config/flowpipe.php -return [ - 'error_handling' => [ - 'default_retry_attempts' => env('FLOWPIPE_RETRY_ATTEMPTS', 3), - 'default_retry_delay' => env('FLOWPIPE_RETRY_DELAY', 100), - 'default_retry_multiplier' => env('FLOWPIPE_RETRY_MULTIPLIER', 2.0), - 'enable_fallback_logging' => env('FLOWPIPE_FALLBACK_LOGGING', true), - 'enable_compensation_logging' => env('FLOWPIPE_COMPENSATION_LOGGING', true), - ], -]; -``` - -This comprehensive error handling system provides the foundation for building resilient, maintainable workflows that can handle various failure scenarios gracefully while providing rich monitoring and debugging capabilities. diff --git a/docs/fixtures-examples.md b/docs/fixtures-examples.md deleted file mode 100644 index 25089c2..0000000 --- a/docs/fixtures-examples.md +++ /dev/null @@ -1,232 +0,0 @@ -# Exemples d'utilisation avec les fixtures - -## Utilisation des fixtures dans les tests - -```php -toHaveKeys([ - 'user_id', - 'action', - 'validated', - 'processed', - 'email_sent', - 'slack_notified' - ]); - } - - /** @test */ - public function it_can_use_nested_flows() - { - $result = GroupsFixture::nestedFlowExample(); - - expect($result)->toHaveKeys([ - 'data', - 'nested_step_1', - 'nested_step_2', - 'main_step' - ]); - } - - /** @test */ - public function it_can_use_custom_steps() - { - $result = Flowpipe::make() - ->send(['user_id' => 1, 'email' => 'test@example.com']) - ->through([ - new AuthenticationStep(['read', 'write']), - new DataValidationStep(['email' => 'email']), - new ApiCallStep('/api/users', 'POST'), - new NotificationStep('email', ['admin@example.com']), - ]) - ->thenReturn(); - - expect($result)->toHaveKeys([ - 'authenticated', - 'is_valid', - 'api_response', - 'notification_sent' - ]); - } -} -``` - -## Utilisation dans une application rรฉelle - -### 1. Configuration des groupes de base - -```php - 'email', - 'name' => 'required', - 'age' => 'numeric' - ]), - new LoggingStep('info', 'Data validation completed'), -]); - -// Groupe de traitement -Flowpipe::group('processing', [ - new DatabaseStep('insert', 'users'), - new CacheStep('user_data', 3600), - new LoggingStep('info', 'User data processed'), -]); - -// Groupe de notifications -Flowpipe::group('notifications', [ - new NotificationStep('email', ['admin@example.com']), - new NotificationStep('slack', ['#general']), - new LoggingStep('info', 'Notifications sent'), -]); -``` - -### 2. Utilisation dans un contrรดleur - -```php -send($request->all()) - ->useGroup('auth') - ->useGroup('validation') - ->nested([ - new FileOperationStep('upload', '/tmp/avatar.jpg'), - new ApiCallStep('/api/external/notify', 'POST'), - ]) - ->useGroup('processing') - ->useGroup('notifications') - ->thenReturn(); - - if (isset($result['error'])) { - return response()->json(['error' => $result['error']], 400); - } - - return response()->json($result); - } -} -``` - -### 3. Workflow complexe avec gestion d'erreurs - -```php -send(['order_id' => 123]) - ->through([ - new ErrorHandlingStep(false, [ - function($error, $payload) { - Log::error('Order processing error', ['error' => $error, 'payload' => $payload]); - } - ]), - 'auth', - 'validation', - ]) - ->nested([ - // Traitement de paiement - new ApiCallStep('/api/payments/process', 'POST'), - new DatabaseStep('update', 'orders', ['status' => 'paid']), - ]) - ->through([ - // Mise ร  jour de l'inventaire - new DatabaseStep('update', 'inventory'), - new CacheStep('inventory_status', 1800), - ]) - ->useGroup('notifications') - ->thenReturn(); -``` - -### 4. Utilisation avec mise en cache - -```php -send(['product_id' => 456]) - ->through([ - new CacheStep('product_details', 3600), - new AuthenticationStep(['read']), - ]) - ->nested([ - new ApiCallStep('/api/products/details', 'GET'), - new DataValidationStep(['price' => 'numeric']), - ]) - ->through([ - new DatabaseStep('select', 'products'), - new FileOperationStep('read', '/storage/product_images'), - ]) - ->thenReturn(); -``` - -### 5. Workflow de test avec groupes avancรฉs - -```php -send(['test_data' => 'value']) - ->through([ - 'advanced-validation', - 'advanced-processing', - ]) - ->nested([ - new LoggingStep('debug', 'Nested processing started'), - new CacheStep('test_cache', 300), - new LoggingStep('debug', 'Nested processing completed'), - ]) - ->thenReturn(); -``` - -## Avantages des fixtures - -1. **Rรฉutilisabilitรฉ** : Les fixtures peuvent รชtre utilisรฉes dans plusieurs tests -2. **Consistance** : Garantit que tous les tests utilisent les mรชmes donnรฉes -3. **Maintenabilitรฉ** : Un seul endroit pour modifier la configuration des tests -4. **Simplicitรฉ** : Simplifie l'รฉcriture et la lecture des tests - -## Bonnes pratiques - -1. **Organisation** : Sรฉparez les fixtures par domaine (Groups, Steps, etc.) -2. **Nommage** : Utilisez des noms explicites pour les mรฉthodes de fixture -3. **Documentation** : Commentez les fixtures complexes -4. **Rรฉutilisation** : Crรฉez des fixtures gรฉnรฉriques rรฉutilisables -5. **Isolation** : Chaque fixture doit รชtre indรฉpendante des autres diff --git a/docs/namespace-resolution.md b/docs/namespace-resolution.md deleted file mode 100644 index 490b433..0000000 --- a/docs/namespace-resolution.md +++ /dev/null @@ -1,81 +0,0 @@ -# Rรฉsolution Intelligente des Namespaces - -Laravel Flowpipe implรฉmente une rรฉsolution intelligente des namespaces pour les classes de steps, permettant une utilisation plus flexible et maintenable. - -## Principe de Fonctionnement - -La rรฉsolution suit cette logique simple : - -1. **Namespace complet** : Si le nom de classe contient un backslash (`\`), il est utilisรฉ tel quel -2. **Namespace relatif** : Si le nom de classe ne contient pas de backslash, il est prรฉfixรฉ avec la configuration `step_namespace` - -## Configuration - -Dans votre fichier `config/flowpipe.php` : - -```php -'step_namespace' => 'App\\Flowpipe\\Steps', -``` - -## Exemples d'Utilisation - -### 1. Namespace Relatif (Recommandรฉ) - -```yaml -flow: user-registration -steps: - - type: action - class: UserRegistration\ValidateInputStep - # Rรฉsolu vers : App\Flowpipe\Steps\UserRegistration\ValidateInputStep - - - type: action - class: SimpleValidationStep - # Rรฉsolu vers : App\Flowpipe\Steps\SimpleValidationStep -``` - -### 2. Namespace Complet - -```yaml -flow: user-registration -steps: - - type: action - class: Examples\Steps\UserRegistration\ValidateInputStep - # Utilisรฉ tel quel : Examples\Steps\UserRegistration\ValidateInputStep - - - type: action - class: My\Custom\Namespace\MyStep - # Utilisรฉ tel quel : My\Custom\Namespace\MyStep -``` - -### 3. Approche Hybride - -```yaml -flow: user-registration -steps: - # Namespace relatif pour les steps de l'application - - type: action - class: UserRegistration\ValidateInputStep - - # Namespace complet pour des steps externes - - type: action - class: ThirdParty\Package\Steps\ExternalStep - - # Classe simple pour des steps utilitaires - - type: action - class: LoggerStep -``` - -## Avantages - -1. **Flexibilitรฉ** : Permet d'utiliser les deux approches selon le besoin -2. **Maintenabilitรฉ** : Les namespaces relatifs sont plus courts et plus faciles ร  maintenir -3. **Rรฉtrocompatibilitรฉ** : Les flows existants continuent de fonctionner -4. **Configuration centralisรฉe** : Un seul endroit pour configurer le namespace par dรฉfaut - -## Cas d'Usage Recommandรฉs - -- **Namespace relatif** : Pour les steps de votre application -- **Namespace complet** : Pour les steps de packages externes ou des exemples -- **Classe simple** : Pour des steps utilitaires simples - -Cette approche offre le meilleur des deux mondes : la simplicitรฉ pour les cas courants et la flexibilitรฉ pour les cas spรฉciaux. diff --git a/docs/queues.md b/docs/queues.md deleted file mode 100644 index 439fc7e..0000000 --- a/docs/queues.md +++ /dev/null @@ -1,491 +0,0 @@ -# Using Flowpipe with Laravel Queues - -Laravel Flowpipe works seamlessly with Laravel's queue system without requiring any special configuration. This guide shows you how to leverage queues for asynchronous processing while keeping your flows simple and maintainable. - -## Basic Queue Integration - -### Simple Job with Flowpipe - -```php -send($this->orderData) - ->through([ - ValidateOrderStep::class, - ProcessPaymentStep::class, - UpdateInventoryStep::class, - SendConfirmationEmailStep::class, - ]) - ->thenReturn(); - - // Handle the result as needed - logger()->info('Order processed', ['result' => $result]); - } -} -``` - -### Dispatching the Job - -```php -// In your controller or service -dispatch(new ProcessOrderJob($orderData)) - ->onQueue('orders'); -``` - -## Advanced Queue Patterns - -### 1. Chained Jobs with Flowpipe - -```php -chain([ - new CreateUserProfileJob($userData), - new SendWelcomeEmailJob($userData), - new SetupUserPreferencesJob($userData), - ]); - } -} - -class ValidateUserJob implements ShouldQueue -{ - use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - - public function __construct(private array $userData) {} - - public function handle(): void - { - $result = Flowpipe::make() - ->send($this->userData) - ->through([ - ValidateEmailStep::class, - ValidatePasswordStep::class, - CheckExistingUserStep::class, - ]) - ->thenReturn(); - - if (!$result['valid']) { - throw new ValidationException('User validation failed'); - } - } -} -``` - -### 2. Parallel Processing with Multiple Queues - -```php -send($orderData) - ->through([ - ValidatePaymentStep::class, - ProcessPaymentStep::class, - ]) - ->thenReturn(); - - if ($paymentResult['payment_status'] !== 'success') { - throw new PaymentException('Payment failed'); - } - - // Dispatch non-critical tasks to different queues - dispatch(new UpdateInventoryJob($orderData)) - ->onQueue('inventory'); - - dispatch(new SendNotificationJob($orderData)) - ->onQueue('notifications'); - - dispatch(new UpdateAnalyticsJob($orderData)) - ->onQueue('analytics'); - } -} -``` - -### 3. Batched Jobs with Flowpipe - -```php -map(fn($chunk) => new ProcessChunkJob($chunk)); - - return Bus::batch($jobs) - ->then(function (Batch $batch) { - // All jobs completed successfully - logger()->info('Batch processing completed', ['batch_id' => $batch->id]); - }) - ->catch(function (Batch $batch, Throwable $e) { - // First batch job failure - logger()->error('Batch processing failed', ['error' => $e->getMessage()]); - }) - ->finally(function (Batch $batch) { - // Cleanup - }) - ->dispatch(); - } -} - -class ProcessChunkJob implements ShouldQueue -{ - use Batchable, Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - - public function __construct(private array $chunk) {} - - public function handle(): void - { - if ($this->batch()->cancelled()) { - return; - } - - $result = Flowpipe::make() - ->send($this->chunk) - ->through([ - ValidateChunkStep::class, - ProcessChunkStep::class, - SaveChunkResultStep::class, - ]) - ->thenReturn(); - - // Update batch progress - $this->batch()->increment('processed_items', count($this->chunk)); - } -} -``` - -## Error Handling in Queued Jobs - -### Retry Logic with Flowpipe Error Handling - -```php -send($this->data) - ->exponentialBackoff(3, 100, 2.0) // Flowpipe-level retry - ->withFallback(function ($payload, $error) { - // Fallback to cached data or default values - return $this->getFallbackData($payload); - }) - ->through([ - CallExternalApiStep::class, - ProcessApiResponseStep::class, - SaveResultStep::class, - ]) - ->thenReturn(); - - } catch (Throwable $e) { - // Log error for monitoring - logger()->error('Job processing failed', [ - 'job' => self::class, - 'attempt' => $this->attempts(), - 'error' => $e->getMessage(), - ]); - - // Re-throw to trigger queue retry - throw $e; - } - } - - public function failed(Throwable $exception): void - { - // Handle final failure - logger()->error('Job permanently failed', [ - 'job' => self::class, - 'error' => $exception->getMessage(), - ]); - } -} -``` - -## Real-World Examples - -### E-commerce Order Processing - -```php -send($this->orderData) - - // Critical path - process synchronously - ->through([ - ValidateOrderStep::class, - CheckInventoryStep::class, - ProcessPaymentStep::class, - ]) - - // After payment success, dispatch async tasks - ->through([ - function($data, $next) { - // Dispatch background tasks - dispatch(new UpdateInventoryJob($data))->onQueue('inventory'); - dispatch(new SendOrderConfirmationJob($data))->onQueue('emails'); - dispatch(new UpdateAnalyticsJob($data))->onQueue('analytics'); - - return $next($data); - }, - ]) - - ->thenReturn(); - } -} -``` - -### User Registration Pipeline - -```php -send($this->userData) - ->through([ - // Synchronous validation - ValidateRegistrationDataStep::class, - CreateUserAccountStep::class, - - // Async post-registration tasks - function($data, $next) { - // Send welcome email asynchronously - dispatch(new SendWelcomeEmailJob($data)) - ->onQueue('emails') - ->delay(now()->addMinutes(5)); - - // Setup user preferences asynchronously - dispatch(new SetupUserPreferencesJob($data)) - ->onQueue('user-setup'); - - // Add to marketing lists asynchronously - dispatch(new AddToMarketingListsJob($data)) - ->onQueue('marketing') - ->delay(now()->addHours(1)); - - return $next($data); - }, - ]) - ->thenReturn(); - } -} -``` - -## Best Practices - -### 1. Queue Configuration - -```php -// config/queue.php -'queues' => [ - 'critical' => [ - 'driver' => 'redis', - 'connection' => 'default', - 'queue' => 'critical', - 'retry_after' => 90, - ], - 'background' => [ - 'driver' => 'redis', - 'connection' => 'default', - 'queue' => 'background', - 'retry_after' => 300, - ], -], -``` - -### 2. Step Design for Queues - -```php -process($payload); - - return $next(array_merge($payload, $result)); - } -} - -// โŒ Avoid: Steps with closures won't serialize -class BadStepWithClosure implements FlowStep -{ - private \Closure $callback; - - public function __construct(\Closure $callback) - { - $this->callback = $callback; // Won't serialize - } -} -``` - -### 3. Monitoring and Observability - -```php -send($this->data) - ->withTracer($tracer) - ->through($this->steps) - ->thenReturn(); - - // Log performance metrics - logger()->info('Flowpipe job completed', [ - 'job' => self::class, - 'steps_count' => $tracer->count(), - 'duration' => $tracer->totalDuration(), - 'memory_peak' => memory_get_peak_usage(true), - ]); - } -} -``` - -## Performance Considerations - -### 1. Memory Management - -```php -// For large datasets, process in chunks -public function handle(): void -{ - $chunks = array_chunk($this->largeDataset, 100); - - foreach ($chunks as $chunk) { - Flowpipe::make() - ->send($chunk) - ->through([ - ProcessChunkStep::class, - ]) - ->thenReturn(); - - // Free memory between chunks - unset($chunk); - } -} -``` - -### 2. Database Connections - -```php -// Ensure database connections are properly handled -public function handle(): void -{ - DB::beginTransaction(); - - try { - $result = Flowpipe::make() - ->send($this->data) - ->through([ - DatabaseStep::class, - AnotherDatabaseStep::class, - ]) - ->thenReturn(); - - DB::commit(); - } catch (Throwable $e) { - DB::rollback(); - throw $e; - } -} -``` - -## Summary - -Laravel Flowpipe works perfectly with Laravel queues without any modifications: - -- โœ… **Simple Integration**: Just wrap your Flowpipe calls in jobs -- โœ… **Full Queue Features**: Support for chains, batches, retries, and delays -- โœ… **Error Handling**: Combine Flowpipe error strategies with queue retry logic -- โœ… **Monitoring**: Full observability with tracers and logging -- โœ… **Performance**: Efficient memory and database connection management - -The key is to design your steps to be stateless and serializable, then leverage Laravel's powerful queue system for orchestration and reliability. diff --git a/docs/step-groups.md b/docs/step-groups.md deleted file mode 100644 index ee6d7cb..0000000 --- a/docs/step-groups.md +++ /dev/null @@ -1,365 +0,0 @@ -# Step Groups & Nesting - -Laravel Flowpipe supports reusable step groups and nested flows for better code organization and modularity. With enhanced Mermaid export capabilities, you can visualize your flow structures with rich color coding for different step types. - -## Overview - -Step Groups allow you to define reusable collections of steps that can be referenced by name in your flows. This promotes code reuse and better organization of complex workflows. - -Nested Flows allow you to create sub-workflows that run independently within your main flow, providing better isolation and modularity. - -## Enhanced Visualization with Group Colors - -Laravel Flowpipe now supports enhanced Mermaid diagrams with rich color coding: - -- **Groups**: Blue theme (`๐Ÿ“ฆ Group elements`) -- **Nested Flows**: Light green theme (`๐Ÿ”„ Nested elements`) -- **Conditional Steps**: Orange theme (`โ“ Conditional elements`) -- **Transform Steps**: Pink theme (`๐Ÿ”„ Transform elements`) -- **Validation Steps**: Green theme (`โœ… Validation elements`) -- **Cache Steps**: Yellow theme (`๐Ÿ’พ Cache elements`) -- **Batch Steps**: Purple theme (`๐Ÿ“Š Batch elements`) -- **Retry Steps**: Red theme (`๐Ÿ”„ Retry elements`) - -You can export any flow or group to see these color-coded visualizations: - -```bash -# Export a group with colors -php artisan flowpipe:export user-validation --type=group --format=mermaid - -# Export a flow with enhanced colors -php artisan flowpipe:export user-processing --format=mermaid -``` - -## Step Groups - -### Defining Step Groups - -```php -use Grazulex\LaravelFlowpipe\Flowpipe; - -// Define a reusable group -Flowpipe::group('user-validation', [ - ValidateEmailStep::class, - CheckUserExistsStep::class, - ValidatePasswordStep::class, -]); - -// Define another group -Flowpipe::group('notifications', [ - SendEmailStep::class, - LogEventStep::class, - UpdateDashboardStep::class, -]); -``` - -### Using Groups in Flows - -You can use groups in two ways: - -#### 1. Through the `through()` method - -```php -$result = Flowpipe::make() - ->send($userData) - ->through([ - 'user-validation', // Reference the group by name - CreateUserStep::class, - 'notifications', - ]) - ->thenReturn(); -``` - -#### 2. Using the `useGroup()` method - -```php -$result = Flowpipe::make() - ->send($userData) - ->useGroup('user-validation') - ->through([ - CreateUserStep::class, - ]) - ->useGroup('notifications') - ->thenReturn(); -``` - -### Group Management - -```php -// Check if a group exists -if (Flowpipe::hasGroup('user-validation')) { - // Use the group -} - -// Get all registered groups -$groups = Flowpipe::getGroups(); - -// Clear all groups (useful for testing) -Flowpipe::clearGroups(); -``` - -## Nested Flows - -Nested flows allow you to create sub-workflows that run independently: - -```php -$result = Flowpipe::make() - ->send($data) - ->nested([ - // This mini-flow runs independently - ValidationStep::class, - TransformStep::class, - ]) - ->through([ - // Main flow continues here - ProcessStep::class, - NotifyStep::class, - ]) - ->thenReturn(); -``` - -### Benefits of Nested Flows - -1. **Isolation**: Each nested flow runs in its own context -2. **Modularity**: Complex logic can be broken down into manageable pieces -3. **Reusability**: Nested flows can be easily extracted and reused -4. **Testing**: Each nested flow can be tested independently - -## Combining Groups and Nesting - -You can combine both features for powerful workflow composition: - -```php -// Define groups for different concerns -Flowpipe::group('validation', [ - ValidateInputStep::class, - SanitizeDataStep::class, -]); - -Flowpipe::group('notifications', [ - SendEmailStep::class, - LogEventStep::class, -]); - -// Use in complex flows -$result = Flowpipe::make() - ->send($data) - ->useGroup('validation') - ->nested([ - // Complex processing in isolation - CalculateMetricsStep::class, - GenerateReportStep::class, - ]) - ->useGroup('notifications') - ->thenReturn(); -``` - -## Advanced Examples - -### E-commerce Order Processing - -```php -// Define reusable groups -Flowpipe::group('order-validation', [ - ValidateOrderDataStep::class, - CheckInventoryStep::class, - ValidatePaymentStep::class, -]); - -Flowpipe::group('order-processing', [ - ProcessPaymentStep::class, - UpdateInventoryStep::class, - CreateOrderStep::class, -]); - -Flowpipe::group('order-fulfillment', [ - GenerateInvoiceStep::class, - SendConfirmationStep::class, - NotifyWarehouseStep::class, -]); - -// Main order processing flow -$result = Flowpipe::make() - ->send($orderData) - ->useGroup('order-validation') - ->nested([ - // Handle special promotions - CheckPromotionsStep::class, - ApplyDiscountsStep::class, - ]) - ->useGroup('order-processing') - ->useGroup('order-fulfillment') - ->thenReturn(); -``` - -### User Registration with Conditional Logic - -```php -Flowpipe::group('user-validation', [ - ValidateEmailStep::class, - CheckPasswordStrengthStep::class, - VerifyUniqueUsernameStep::class, -]); - -Flowpipe::group('user-setup', [ - CreateUserStep::class, - SetupUserPreferencesStep::class, - CreateUserDirectoryStep::class, -]); - -$result = Flowpipe::make() - ->send($userData) - ->useGroup('user-validation') - ->when('email_verified', [ - 'user-setup', - ]) - ->nested([ - // Send welcome sequence - SendWelcomeEmailStep::class, - ScheduleOnboardingStep::class, - ]) - ->unless('email_verified', [ - SendVerificationEmailStep::class, - ]) - ->thenReturn(); -``` - -## Best Practices - -### 1. Group Organization - -- Group related steps together -- Use descriptive group names -- Keep groups focused on a single responsibility - -```php -// Good -Flowpipe::group('user-validation', [ - ValidateEmailStep::class, - CheckUserExistsStep::class, -]); - -// Avoid -Flowpipe::group('mixed-stuff', [ - ValidateEmailStep::class, - SendEmailStep::class, - UpdateDatabaseStep::class, -]); -``` - -### 2. Nested Flow Usage - -- Use nested flows for complex, related operations -- Keep nested flows small and focused -- Consider extracting complex nested flows into separate methods - -```php -// Good -->nested([ - CalculateShippingStep::class, - ApplyShippingDiscountStep::class, -]) - -// Consider refactoring if too complex -->nested([ - // Too many steps here... -]) -``` - -### 3. Performance Considerations - -- Groups are resolved at runtime, so there's minimal overhead -- Nested flows create new Flowpipe instances, which has a small memory cost -- Use caching for frequently used groups in high-traffic applications - -### 4. Testing - -- Test groups independently -- Use fixtures for complex group setups -- Test nested flows in isolation - -```php -public function test_user_validation_group() -{ - $result = Flowpipe::make() - ->send($testData) - ->useGroup('user-validation') - ->thenReturn(); - - $this->assertTrue($result['is_valid']); -} -``` - -## Error Handling - -Groups and nested flows inherit the error handling behavior of their parent flow: - -```php -$result = Flowpipe::make() - ->send($data) - ->useGroup('validation') - ->nested([ - // If this fails, the error bubbles up - RiskyOperationStep::class, - ]) - ->through([ - // This step can handle errors from nested flow - ErrorHandlingStep::class, - ]) - ->thenReturn(); -``` - -## API Reference - -### Static Methods - -- `Flowpipe::group(string $name, array $steps)` - Define a step group -- `Flowpipe::getGroups()` - Get all registered groups -- `Flowpipe::hasGroup(string $name)` - Check if a group exists -- `Flowpipe::clearGroups()` - Clear all registered groups - -### Instance Methods - -- `useGroup(string $name)` - Add a group to the flow -- `nested(array $steps)` - Create a nested flow - -## Enhanced Mermaid Export Examples - -### Exporting Groups with Colors - -```php -// Define a colorful group -Flowpipe::group('data-processing', [ - // Transform steps will appear in pink - fn($data, $next) => $next(array_map('strtoupper', $data)), - // Validation steps will appear in green - fn($data, $next) => $next(array_filter($data, fn($item) => strlen($item) > 0)), - // Cache steps will appear in yellow - fn($data, $next) => $next(Cache::remember('processed-' . md5(serialize($data)), 3600, fn() => $data)), -]); - -// Export this group to see the color-coded visualization -// php artisan flowpipe:export data-processing --type=group --format=mermaid -``` - -### Complex Flow with Multiple Colors - -```php -$result = Flowpipe::make() - ->send($data) - ->useGroup('data-processing') // Blue group box - ->nested([ // Light green nested flow - fn($data, $next) => $next(array_merge($data, ['processed' => true])), - ]) - ->transform(fn($data) => $data) // Pink transform step - ->validate(['required' => true]) // Green validation step - ->cache('result-key', 3600) // Yellow cache step - ->batch(100) // Purple batch step - ->retry(3) // Red retry step - ->thenReturn(); - -// Export this flow to see all the different colored steps -// php artisan flowpipe:export complex-flow --format=mermaid -``` - -For more examples and advanced usage, see the [examples directory](../examples/). diff --git a/docs/testing.md b/docs/testing.md deleted file mode 100644 index 28e8846..0000000 --- a/docs/testing.md +++ /dev/null @@ -1,1187 +0,0 @@ -# Testing - -Laravel Flowpipe provides comprehensive testing utilities to help you test your flows, steps, and conditions effectively. - -## Testing Setup - -### Basic Test Structure - -```php -send(['user_id' => 1]) - ->through([ - fn($data, $next) => $next(array_merge($data, ['processed' => true])), - ]) - ->thenReturn(); - - $this->assertTrue($result['processed']); - $this->assertEquals(1, $result['user_id']); - } -} -``` - -### Test Utilities - -Laravel Flowpipe provides several testing utilities: - -#### 1. Test Tracer - -Use the `TestTracer` to capture flow execution details: - -```php -send(['order_id' => 123]) - ->through([ - fn($data, $next) => $next(array_merge($data, ['validated' => true])), - fn($data, $next) => $next(array_merge($data, ['processed' => true])), - ]) - ->withTracer($tracer) - ->thenReturn(); - - // Assert flow completed successfully - $this->assertTrue($result['processed']); - - // Assert specific steps were executed - $this->assertEquals(2, $tracer->count()); - $this->assertEquals('Closure', $tracer->firstStep()); - $this->assertEquals('Closure', $tracer->lastStep()); - - // Get all step names - $steps = $tracer->steps(); - $this->assertCount(2, $steps); - - // Get all execution logs - $logs = $tracer->all(); - $this->assertCount(2, $logs); - - // Check first log details - $firstLog = $logs[0]; - $this->assertEquals('Closure', $firstLog['step']); - $this->assertEquals(['order_id' => 123], $firstLog['before']); - $this->assertEquals(['order_id' => 123, 'validated' => true], $firstLog['after']); - $this->assertIsFloat($firstLog['duration']); - } -} -``` - -#### 2. Custom Step Testing - -Create and test custom steps: - -```php -send(['data' => 'test']) - ->through([$step]) - ->thenReturn(); - - $this->assertTrue($result['custom_processed']); - $this->assertEquals('test', $result['data']); - } -} -``` - -#### 3. Test Conditions - -Test conditions in isolation: - -```php - 0; - } - }; - - // Test with sufficient inventory - $result = Flowpipe::make() - ->send(['inventory_count' => 10]) - ->through([ - ConditionalStep::when($condition, fn($data, $next) => $next(array_merge($data, ['has_inventory' => true]))) - ]) - ->thenReturn(); - - $this->assertTrue($result['has_inventory']); - - // Test with insufficient inventory - $result = Flowpipe::make() - ->send(['inventory_count' => 0]) - ->through([ - ConditionalStep::when($condition, fn($data, $next) => $next(array_merge($data, ['has_inventory' => true]))) - ]) - ->thenReturn(); - - $this->assertFalse($result['has_inventory'] ?? false); - } -} -``` - -## Testing Individual Steps - -### Step Unit Tests - -```php - true])); - } - }; - - // Test with valid input - $result = Flowpipe::make() - ->send([ - 'email' => 'user@example.com', - 'password' => 'password123' - ]) - ->through([$step]) - ->thenReturn(); - - $this->assertTrue($result['validation_passed']); - } - - public function test_fails_with_invalid_input() - { - $step = new class implements FlowStep { - public function handle(mixed $payload, \Closure $next): mixed - { - if (!is_array($payload)) { - throw new \InvalidArgumentException('Payload must be an array'); - } - - if (empty($payload['email']) || !filter_var($payload['email'], FILTER_VALIDATE_EMAIL)) { - throw new \InvalidArgumentException('Valid email is required'); - } - - if (empty($payload['password']) || strlen($payload['password']) < 8) { - throw new \InvalidArgumentException('Password must be at least 8 characters'); - } - - return $next(array_merge($payload, ['validation_passed' => true])); - } - }; - - // Test with invalid input - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('Valid email is required'); - - Flowpipe::make() - ->send([ - 'email' => 'invalid-email', - 'password' => '' - ]) - ->through([$step]) - ->thenReturn(); - } -} -``` - -### Step Integration Tests - -```php - $payload['name'], - 'email' => $payload['email'], - 'password' => Hash::make($payload['password']), - ]); - - return $next(array_merge($payload, [ - 'user_id' => $user->id, - 'user_created' => true - ])); - } - }; - - $result = Flowpipe::make() - ->send([ - 'email' => 'user@example.com', - 'password' => 'password123', - 'name' => 'John Doe' - ]) - ->through([$step]) - ->thenReturn(); - - $this->assertTrue($result['user_created']); - $this->assertDatabaseHas('users', [ - 'email' => 'user@example.com', - 'name' => 'John Doe' - ]); - - $user = User::where('email', 'user@example.com')->first(); - $this->assertEquals($user->id, $result['user_id']); - } -} -``` - -## Flow Integration Tests - -### Complete Flow Tests - -```php -create(); - $product = Product::factory()->create(['stock' => 10]); - - // Create flow steps - $validateStep = new class implements FlowStep { - public function handle(mixed $payload, \Closure $next): mixed - { - $product = Product::find($payload['product_id']); - if ($product->stock < $payload['quantity']) { - throw new \InvalidArgumentException('Insufficient inventory'); - } - return $next(array_merge($payload, ['validated' => true])); - } - }; - - $createOrderStep = new class implements FlowStep { - public function handle(mixed $payload, \Closure $next): mixed - { - $order = Order::create([ - 'user_id' => $payload['user_id'], - 'product_id' => $payload['product_id'], - 'quantity' => $payload['quantity'], - 'status' => 'completed', - ]); - - return $next(array_merge($payload, ['order_id' => $order->id])); - } - }; - - $updateInventoryStep = new class implements FlowStep { - public function handle(mixed $payload, \Closure $next): mixed - { - $product = Product::find($payload['product_id']); - $product->update(['stock' => $product->stock - $payload['quantity']]); - - return $next(array_merge($payload, ['inventory_updated' => true])); - } - }; - - $result = Flowpipe::make() - ->send([ - 'user_id' => $user->id, - 'product_id' => $product->id, - 'quantity' => 2, - 'payment_method' => 'credit_card' - ]) - ->through([ - $validateStep, - $createOrderStep, - $updateInventoryStep, - ]) - ->thenReturn(); - - // Assert flow completed successfully - $this->assertTrue($result['validated']); - $this->assertTrue($result['inventory_updated']); - - // Assert order was created - $this->assertDatabaseHas('orders', [ - 'user_id' => $user->id, - 'status' => 'completed' - ]); - - // Assert inventory was updated - $product->refresh(); - $this->assertEquals(8, $product->stock); - - // Assert result contains expected data - $this->assertArrayHasKey('order_id', $result); - } - - public function test_order_processing_with_insufficient_inventory() - { - $user = User::factory()->create(); - $product = Product::factory()->create(['stock' => 1]); - - $validateStep = new class implements FlowStep { - public function handle(mixed $payload, \Closure $next): mixed - { - $product = Product::find($payload['product_id']); - if ($product->stock < $payload['quantity']) { - throw new \InvalidArgumentException('Insufficient inventory'); - } - return $next(array_merge($payload, ['validated' => true])); - } - }; - - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('Insufficient inventory'); - - Flowpipe::make() - ->send([ - 'user_id' => $user->id, - 'product_id' => $product->id, - 'quantity' => 5, // More than available - 'payment_method' => 'credit_card' - ]) - ->through([$validateStep]) - ->thenReturn(); - - // Assert no order was created - $this->assertDatabaseMissing('orders', [ - 'user_id' => $user->id - ]); - - // Assert inventory wasn't changed - $product->refresh(); - $this->assertEquals(1, $product->stock); - } -} -``` - -## Error Handling Tests - -### Testing Error Scenarios - -```php -expectException(\Exception::class); - $this->expectExceptionMessage('Payment failed'); - - Flowpipe::make() - ->send(['amount' => 100]) - ->through([$failingStep]) - ->thenReturn(); - } - - public function test_flow_with_retry_step() - { - $result = Flowpipe::make() - ->send(['data' => 'test']) - ->retry(3, 100, function ($exception) { - return $exception instanceof \RuntimeException; - }) - ->through([ - fn($data, $next) => $next(array_merge($data, ['processed' => true])), - ]) - ->thenReturn(); - - $this->assertTrue($result['processed']); - } -} -``` - -## Performance Tests - -### Testing Flow Performance - -```php -send(['items' => range(1, 1000)]) - ->through([ - fn($data, $next) => $next(array_merge($data, ['processed' => true])), - fn($data, $next) => $next(array_merge($data, ['validated' => true])), - ]) - ->thenReturn(); - - $endTime = microtime(true); - $executionTime = $endTime - $startTime; - - $this->assertTrue($result['processed']); - $this->assertLessThan(1.0, $executionTime, 'Flow should complete within 1 second'); - } - - public function test_flow_memory_usage() - { - $memoryBefore = memory_get_usage(); - - $result = Flowpipe::make() - ->send(['large_dataset' => str_repeat('data', 10000)]) - ->through([ - fn($data, $next) => $next(array_merge($data, ['processed' => true])), - ]) - ->thenReturn(); - - $memoryAfter = memory_get_usage(); - $memoryUsed = $memoryAfter - $memoryBefore; - - $this->assertTrue($result['processed']); - $this->assertLessThan(10 * 1024 * 1024, $memoryUsed, 'Flow should use less than 10MB'); - } -} -``` - -## Test Data Management - -### Factory Methods - -```php - 'user@example.com', - 'password' => 'password123', - 'name' => 'John Doe', - 'terms_accepted' => true, - ], $overrides); - } - - protected function createOrderData(array $overrides = []): array - { - return array_merge([ - 'user_id' => 1, - 'product_id' => 1, - 'quantity' => 1, - 'payment_method' => 'credit_card', - 'shipping_address' => '123 Main St', - ], $overrides); - } - - public function test_user_registration_flow() - { - $data = $this->createUserRegistrationData(); - - $result = Flowpipe::make() - ->send($data) - ->through([ - fn($data, $next) => $next(array_merge($data, ['processed' => true])), - ]) - ->thenReturn(); - - $this->assertTrue($result['processed']); - $this->assertEquals('user@example.com', $result['email']); - } - - public function test_order_processing_flow() - { - $data = $this->createOrderData(['quantity' => 5]); - - $result = Flowpipe::make() - ->send($data) - ->through([ - fn($data, $next) => $next(array_merge($data, ['validated' => true])), - ]) - ->thenReturn(); - - $this->assertTrue($result['validated']); - $this->assertEquals(5, $result['quantity']); - } -} -``` - -## Testing Configuration - -### PHPUnit Configuration - -```xml - - - - - ./tests/Feature - - - ./tests/Unit - - - ./tests/Performance - - - - - - ./src - - - -``` - -### Test Environment Configuration - -```php -// config/flowpipe.php (testing environment) -return [ - 'definitions_path' => 'tests/fixtures/flows', - 'step_namespace' => 'Tests\\Fixtures\\Steps', - 'tracing' => [ - 'enabled' => true, - 'default' => \Grazulex\LaravelFlowpipe\Tracer\TestTracer::class, - ], -]; -``` - -## Best Practices - -### 1. Test Organization - -``` -tests/ -โ”œโ”€โ”€ Feature/ -โ”‚ โ”œโ”€โ”€ FlowExecutionTest.php -โ”‚ โ”œโ”€โ”€ OrderProcessingFlowTest.php -โ”‚ โ””โ”€โ”€ UserRegistrationFlowTest.php -โ”œโ”€โ”€ Unit/ -โ”‚ โ”œโ”€โ”€ Steps/ -โ”‚ โ”‚ โ”œโ”€โ”€ ValidateInputStepTest.php -โ”‚ โ”‚ โ””โ”€โ”€ CreateUserStepTest.php -โ”‚ โ”œโ”€โ”€ Conditions/ -โ”‚ โ”‚ โ””โ”€โ”€ HasInventoryConditionTest.php -โ”‚ โ””โ”€โ”€ TracerTest.php -โ”œโ”€โ”€ Performance/ -โ”‚ โ””โ”€โ”€ FlowPerformanceTest.php -โ””โ”€โ”€ fixtures/ - โ””โ”€โ”€ flows/ - โ”œโ”€โ”€ test-flow.yaml - โ””โ”€โ”€ mock-flow.yaml -``` - -### 2. Test Naming - -```php -// Good test names -public function test_user_registration_creates_user_successfully() -public function test_order_processing_fails_with_insufficient_inventory() -public function test_payment_step_handles_declined_cards() - -// Bad test names -public function test_flow() -public function test_step_works() -public function test_condition() -``` - -### 3. Assertions - -```php -// Specific assertions -$this->assertTrue($result['processed']); -$this->assertArrayHasKey('user_id', $result); -$this->assertEquals('completed', $result['status']); - -// Avoid generic assertions -$this->assertNotNull($result); -$this->assertTrue(true); -``` - -### 4. Test Data - -```php -// Use factories for complex data -$user = User::factory()->create(); -$order = Order::factory()->create(['user_id' => $user->id]); - -// Use builders for simple data -$data = [ - 'email' => 'test@example.com', - 'amount' => 100, -]; -``` - -## Continuous Integration - -### GitHub Actions - -```yaml -# .github/workflows/tests.yml -name: Tests - -on: [push, pull_request] - -jobs: - test: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.3' - - - name: Install dependencies - run: composer install --no-progress --prefer-dist --optimize-autoloader - - - name: Run tests - run: vendor/bin/pest --coverage - - - name: Upload coverage - uses: codecov/codecov-action@v3 -``` - -## Advanced Testing Scenarios - -### Testing with Batch Processing - -```php -public function test_batch_processing_flow() -{ - $items = range(1, 100); - - $result = Flowpipe::make() - ->send(['items' => $items]) - ->batch(10) - ->through([ - fn($data, $next) => $next(array_merge($data, ['processed' => true])), - ]) - ->thenReturn(); - - $this->assertTrue($result['processed']); - $this->assertCount(100, $result['items']); -} -``` - -### Testing with Caching - -```php -public function test_flow_with_caching() -{ - $result = Flowpipe::make() - ->send(['data' => 'test']) - ->cache('test-key', 300) - ->through([ - fn($data, $next) => $next(array_merge($data, ['cached' => true])), - ]) - ->thenReturn(); - - $this->assertTrue($result['cached']); -} -``` - -### Testing with Validation - -```php -public function test_flow_with_validation() -{ - $result = Flowpipe::make() - ->send(['email' => 'test@example.com', 'name' => 'John']) - ->validate([ - 'email' => 'required|email', - 'name' => 'required|string', - ]) - ->through([ - fn($data, $next) => $next(array_merge($data, ['validated' => true])), - ]) - ->thenReturn(); - - $this->assertTrue($result['validated']); -} -``` - -This comprehensive testing guide covers all aspects of testing Laravel Flowpipe flows using the actual implementation, from unit tests to integration tests and performance testing. - -## Testing Flow Validation - -Laravel Flowpipe includes comprehensive validation testing to ensure your YAML flow definitions are correct and properly tested. - -### Testing Flow Definition Validation - -```php -validator = new FlowDefinitionValidator(); - } - - public function test_validates_correct_flow_definition() - { - $definition = [ - 'flow' => 'TestFlow', - 'description' => 'Test flow definition', - 'steps' => [ - [ - 'type' => 'closure', - 'action' => 'test_action' - ] - ] - ]; - - $result = $this->validator->validateFlowDefinition($definition); - - $this->assertTrue($result->isValid()); - $this->assertEmpty($result->errors); - } - - public function test_detects_missing_required_fields() - { - $definition = [ - 'description' => 'Test flow definition', - // Missing 'flow' field - 'steps' => [ - [ - 'type' => 'closure', - 'action' => 'test_action' - ] - ] - ]; - - $result = $this->validator->validateFlowDefinition($definition); - - $this->assertFalse($result->isValid()); - $this->assertContains("Missing required field: 'flow'", $result->errors); - } - - public function test_detects_invalid_step_types() - { - $definition = [ - 'flow' => 'TestFlow', - 'steps' => [ - [ - 'type' => 'invalid_type', - 'action' => 'test_action' - ] - ] - ]; - - $result = $this->validator->validateFlowDefinition($definition); - - $this->assertFalse($result->isValid()); - $this->assertContains("Unsupported step type: 'invalid_type'", $result->errors); - } - - public function test_validates_condition_steps() - { - $definition = [ - 'flow' => 'TestFlow', - 'steps' => [ - [ - 'type' => 'condition', - 'condition' => [ - 'field' => 'active', - 'operator' => 'equals', - 'value' => true - ], - 'step' => [ - 'type' => 'closure', - 'action' => 'handle_active' - ] - ] - ] - ]; - - $result = $this->validator->validateFlowDefinition($definition); - - $this->assertTrue($result->isValid()); - } - - public function test_detects_invalid_condition_operators() - { - $definition = [ - 'flow' => 'TestFlow', - 'steps' => [ - [ - 'type' => 'condition', - 'condition' => [ - 'field' => 'active', - 'operator' => 'not_supported', - 'value' => true - ], - 'step' => [ - 'type' => 'closure', - 'action' => 'handle_active' - ] - ] - ] - ]; - - $result = $this->validator->validateFlowDefinition($definition); - - $this->assertFalse($result->isValid()); - $this->assertContains("Unsupported operator: 'not_supported'", $result->errors); - } - - public function test_validates_nested_flows() - { - $definition = [ - 'flow' => 'TestFlow', - 'steps' => [ - [ - 'type' => 'nested', - 'steps' => [ - [ - 'type' => 'closure', - 'action' => 'nested_action' - ] - ] - ] - ] - ]; - - $result = $this->validator->validateFlowDefinition($definition); - - $this->assertTrue($result->isValid()); - } -} -``` - -### Testing Validation Command - -```php -testFlowsPath = storage_path('test-flows'); - File::ensureDirectoryExists($this->testFlowsPath); - } - - protected function tearDown(): void - { - File::deleteDirectory($this->testFlowsPath); - parent::tearDown(); - } - - public function test_validates_all_flows_with_success() - { - // Create a valid flow file - File::put($this->testFlowsPath . '/valid-flow.yaml', ' -flow: ValidFlow -description: A valid flow for testing -steps: - - type: closure - action: test_action - '); - - $this->artisan('flowpipe:validate', [ - '--all' => true, - '--path' => $this->testFlowsPath - ]) - ->expectsOutput('All flows are valid') - ->assertExitCode(0); - } - - public function test_validates_all_flows_with_errors() - { - // Create an invalid flow file - File::put($this->testFlowsPath . '/invalid-flow.yaml', ' -flow: InvalidFlow -description: An invalid flow for testing -# Missing steps field - '); - - $this->artisan('flowpipe:validate', [ - '--all' => true, - '--path' => $this->testFlowsPath - ]) - ->expectsOutput('Validation failed') - ->assertExitCode(1); - } - - public function test_validates_single_flow() - { - // Create a valid flow file - File::put($this->testFlowsPath . '/single-flow.yaml', ' -flow: SingleFlow -description: A single flow for testing -steps: - - type: closure - action: test_action - '); - - $this->artisan('flowpipe:validate', [ - '--path' => $this->testFlowsPath . '/single-flow.yaml' - ]) - ->assertExitCode(0); - } - - public function test_validates_with_json_output() - { - // Create a flow file - File::put($this->testFlowsPath . '/json-test.yaml', ' -flow: JsonTest -description: Flow for JSON output testing -steps: - - type: closure - action: test_action - '); - - $this->artisan('flowpipe:validate', [ - '--all' => true, - '--format' => 'json', - '--path' => $this->testFlowsPath - ]) - ->assertExitCode(0); - } -} -``` - -### Testing Validation in CI/CD - -```php -artisan('flowpipe:validate', [ - '--all' => true, - '--format' => 'json' - ]) - ->assertExitCode(0); - - // Assert that all flows are valid for deployment - $this->artisan('flowpipe:validate', ['--all' => true]) - ->expectsOutput('All flows are valid') - ->assertExitCode(0); - } - - public function test_validation_prevents_invalid_deployment() - { - // Create a temporarily invalid flow - $invalidFlow = storage_path('app/invalid-test.yaml'); - File::put($invalidFlow, ' -flow: InvalidDeploymentTest -description: Invalid flow to test CI prevention -# Missing steps field - '); - - $this->artisan('flowpipe:validate', [ - '--path' => $invalidFlow - ]) - ->assertExitCode(1); - - // Clean up - File::delete($invalidFlow); - } -} -``` - -### Integration Testing with Validation - -```php - 'IntegrationTest', - 'steps' => [ - [ - 'type' => 'closure', - 'action' => 'test_action' - ] - ] - ]; - - $validator = new \Grazulex\LaravelFlowpipe\Validation\FlowDefinitionValidator(); - $result = $validator->validateFlowDefinition($validDefinition); - - $this->assertTrue($result->isValid()); - - // Now test the actual flow execution - $flowResult = Flowpipe::make() - ->send(['test' => 'data']) - ->through([ - fn($data, $next) => $next(array_merge($data, ['processed' => true])) - ]) - ->thenReturn(); - - $this->assertTrue($flowResult['processed']); - } -} -``` - -This comprehensive validation testing section ensures that your flow definitions are properly validated both during development and in CI/CD pipelines. diff --git a/docs/usage-examples.md b/docs/usage-examples.md deleted file mode 100644 index 579788b..0000000 --- a/docs/usage-examples.md +++ /dev/null @@ -1,375 +0,0 @@ -# Laravel Flowpipe Usage Examples - -This document demonstrates the complete usage of Laravel Flowpipe with all available features, including comprehensive error handling patterns. - -## Basic Usage - -```php -send(' hello world ') - ->through([ - fn($text, $next) => $next(trim($text)), - fn($text, $next) => $next(ucwords($text)), - fn($text, $next) => $next(str_replace(' ', '-', $text)), - ]) - ->thenReturn(); - -echo $result; // "Hello-World" -``` - -## Error Handling Patterns - -### Basic Error Handling - -```php -use Grazulex\LaravelFlowpipe\Flowpipe; - -// Retry with exponential backoff -$result = Flowpipe::make() - ->send(['api_endpoint' => 'https://api.example.com/data']) - ->exponentialBackoff(3, 100, 2.0) // 3 attempts, 100ms base delay, 2x multiplier - ->through([ - fn($data, $next) => $next(file_get_contents($data['api_endpoint'])), - ]) - ->thenReturn(); - -// Fallback to cached data -$result = Flowpipe::make() - ->send(['user_id' => 123]) - ->withFallback(fn($payload, $error) => Cache::get("user_{$payload['user_id']}", [])) - ->through([ - fn($data, $next) => $next(fetchUserFromDatabase($data['user_id'])), - ]) - ->thenReturn(); -``` - -### Production-Ready Error Handling - -```php -use Grazulex\LaravelFlowpipe\ErrorHandling\Strategies\CompositeStrategy; -use Grazulex\LaravelFlowpipe\ErrorHandling\Strategies\RetryStrategy; -use Grazulex\LaravelFlowpipe\ErrorHandling\Strategies\FallbackStrategy; -use Grazulex\LaravelFlowpipe\ErrorHandling\Strategies\CompensationStrategy; - -// E-commerce order processing with comprehensive error handling -$orderResult = Flowpipe::make() - ->send($orderData) - - // Step 1: Validate order with fallback - ->withFallback(function ($payload, $error) { - Log::warning('Order validation failed, using basic validation', [ - 'order_id' => $payload['order_id'], - 'error' => $error->getMessage() - ]); - return array_merge($payload, ['validation_mode' => 'basic']); - }) - ->through([ - fn($data, $next) => $next(validateOrderComprehensive($data)), - ]) - - // Step 2: Process payment with retry and compensation - ->withErrorHandler( - CompositeStrategy::make() - ->retry(RetryStrategy::exponentialBackoff(3, 200, 2.0)) - ->compensate(CompensationStrategy::make(function ($payload, $error, $context) { - // Rollback any partial payment processing - if (isset($payload['payment_intent_id'])) { - cancelPaymentIntent($payload['payment_intent_id']); - } - return array_merge($payload, ['payment_cancelled' => true]); - })) - ) - ->through([ - fn($data, $next) => $next(processPayment($data)), - ]) - - // Step 3: Update inventory with fallback - ->withFallback(function ($payload, $error) { - // Queue for manual inventory processing - QueueManualInventoryUpdate::dispatch($payload); - return array_merge($payload, ['inventory_queued' => true]); - }) - ->through([ - fn($data, $next) => $next(updateInventory($data)), - ]) - - ->thenReturn(); -``` - -## Using Built-in Steps - -```php -send(['email' => 'john@example.com', 'name' => 'John Doe']) - ->validate([ - 'email' => 'required|email', - 'name' => 'required|string|max:255', - ]) - ->transform(fn($data) => array_merge($data, ['processed_at' => now()])) - ->cache('user-data', 300) - ->through([ - fn($data, $next) => $next(array_merge($data, ['saved' => true])), - ]) - ->thenReturn(); - -var_dump($result); -``` - -## Using Conditional Steps - -```php -send(['name' => 'John', 'role' => 'admin']) - ->through([ - ConditionalStep::when( - new IsAdminCondition(), - fn($data, $next) => $next(array_merge($data, ['permissions' => ['read', 'write', 'delete']])) - ), - ConditionalStep::unless( - new IsAdminCondition(), - fn($data, $next) => $next(array_merge($data, ['permissions' => ['read']])) - ), - ]) - ->thenReturn(); - -var_dump($result); -``` - -## Using Custom Steps - -```php - true])); - } -} - -class CreateUserStep implements FlowStep -{ - public function handle(mixed $payload, \Closure $next): mixed - { - // Simulate user creation - $payload['id'] = rand(1000, 9999); - $payload['created_at'] = now(); - - return $next($payload); - } -} - -$result = Flowpipe::make() - ->send(['email' => 'john@example.com', 'name' => 'John Doe']) - ->through([ - new ValidateUserStep(), - new CreateUserStep(), - ]) - ->thenReturn(); - -var_dump($result); -``` - -## Using Tracers - -```php -send(['data' => 'test']) - ->through([ - fn($data, $next) => $next(array_merge($data, ['step1' => true])), - fn($data, $next) => $next(array_merge($data, ['step2' => true])), - ]) - ->withTracer($tracer) - ->thenReturn(); - -echo "Steps executed: " . $tracer->count() . "\n"; -echo "First step: " . $tracer->firstStep() . "\n"; -echo "Last step: " . $tracer->lastStep() . "\n"; - -// Get all logs -$logs = $tracer->all(); -foreach ($logs as $log) { - echo "Step: {$log['step']}\n"; - echo "Duration: {$log['duration']}ms\n"; - echo "Before: " . json_encode($log['before']) . "\n"; - echo "After: " . json_encode($log['after']) . "\n"; - echo "---\n"; -} -``` - -## Error Handling - -```php - 5) { - throw new \RuntimeException('Random failure'); - } - - return $next($payload); - } -} - -try { - $result = Flowpipe::make() - ->send(['data' => 'test']) - ->retry(3, 100) // Retry up to 3 times with 100ms delay - ->through([ - new FailingStep(), - ]) - ->thenReturn(); - - echo "Success: " . json_encode($result) . "\n"; -} catch (\Exception $e) { - echo "Failed: " . $e->getMessage() . "\n"; -} -``` - -## Performance Monitoring - -```php -send(range(1, 1000)) - ->batch(100) - ->through([ - fn($batch, $next) => $next(array_map(fn($x) => $x * 2, $batch)), - fn($batch, $next) => $next(array_filter($batch, fn($x) => $x > 100)), - ]) - ->withTracer($tracer) - ->thenReturn(); - -echo "Total execution time: " . $tracer->getTotalTime() . "ms\n"; -echo "Peak memory usage: " . $tracer->getPeakMemoryUsage() . " bytes\n"; -``` - -## Using with Laravel Models - -```php - $payload['name'], - 'email' => $payload['email'], - 'password' => bcrypt($payload['password']), - ]); - - return $next(array_merge($payload, ['user' => $user])); - } -} - -class SendWelcomeEmailStep implements FlowStep -{ - public function handle(mixed $payload, \Closure $next): mixed - { - Mail::to($payload['user']->email)->send(new WelcomeEmail($payload['user'])); - - return $next(array_merge($payload, ['email_sent' => true])); - } -} - -$result = Flowpipe::make() - ->send([ - 'name' => 'John Doe', - 'email' => 'john@example.com', - 'password' => 'password123', - ]) - ->validate([ - 'name' => 'required|string|max:255', - 'email' => 'required|email|unique:users', - 'password' => 'required|min:8', - ]) - ->through([ - new CreateUserStep(), - new SendWelcomeEmailStep(), - ]) - ->thenReturn(); - -echo "User created: " . $result['user']->id . "\n"; -echo "Welcome email sent: " . ($result['email_sent'] ? 'Yes' : 'No') . "\n"; -``` - -## Running from Command Line - -```bash -# Create a flow definition -php artisan flowpipe:make-flow user-registration - -# Run the flow -php artisan flowpipe:run user-registration --payload='{"name":"John","email":"john@example.com"}' - -# List all flows -php artisan flowpipe:list --detailed - -# Export flow to different formats -php artisan flowpipe:export user-registration --format=json -php artisan flowpipe:export user-registration --format=mermaid -php artisan flowpipe:export user-registration --format=md --output=docs/user-registration.md -``` - -This comprehensive example shows how to use all the features of Laravel Flowpipe effectively. \ No newline at end of file diff --git a/docs/validation.md b/docs/validation.md deleted file mode 100644 index ad3a6c9..0000000 --- a/docs/validation.md +++ /dev/null @@ -1,350 +0,0 @@ -# Flow Validation Guide - -Laravel Flowpipe provides comprehensive validation for YAML flow definitions to ensure they are correctly structured and all references are valid. - -## Overview - -The `flowpipe:validate` command analyzes your flow definitions and provides detailed feedback on: - -- **Structure validation**: Ensures all required fields are present -- **Step type validation**: Validates that only supported step types are used -- **Reference validation**: Checks that groups and step classes exist -- **Condition validation**: Validates condition operators and structure -- **YAML syntax validation**: Catches YAML formatting errors - -## Command Usage - -### Basic Validation - -```bash -# Validate all flows -php artisan flowpipe:validate --all - -# Validate specific flow -php artisan flowpipe:validate --path=user-registration.yaml -``` - -### Output Formats - -```bash -# Table format (default) -php artisan flowpipe:validate --all --format=table - -# JSON format (for CI/CD integration) -php artisan flowpipe:validate --all --format=json -``` - -### Custom Path - -```bash -# Validate flows in custom directory -php artisan flowpipe:validate --all --path=custom/flows -``` - -## Validation Rules - -### Required Fields - -Every flow definition must have: -- `flow`: The flow name -- `steps`: An array of steps to execute - -### Supported Step Types - -- `action`: Execute an action (legacy alias for `step`) -- `closure`: Execute a closure action -- `step`: Execute a step class -- `condition`: Execute conditional logic -- `group`: Execute a predefined group -- `nested`: Execute nested flow steps - -### Supported Condition Operators - -- `equals`: Exact match -- `contains`: Contains substring -- `greater_than`: Numeric comparison -- `less_than`: Numeric comparison -- `in`: Value in array - -### Flow Name Requirements - -- Must start with a letter or underscore -- Can contain letters, numbers, underscores, and hyphens -- Must be unique within the definitions directory - -## Common Validation Errors - -### 1. Missing Required Fields - -**Error**: `Missing required field: 'flow'` -**Cause**: YAML file doesn't have a `flow` field -**Fix**: Add `flow: YourFlowName` to the YAML file - -### 2. Invalid Step Types - -**Error**: `Unsupported step type 'custom_step'` -**Cause**: Using a step type that's not supported -**Fix**: Use one of: `closure`, `step`, `condition`, `group`, `nested` - -### 3. Missing Step Fields - -**Error**: `Closure step missing 'action' field` -**Cause**: `closure` step without `action` field -**Fix**: Add `action: your_action_name` to the closure step - -### 4. Invalid Operators - -**Error**: `Unsupported operator 'not_equal'` -**Cause**: Using an unsupported condition operator -**Fix**: Use one of: `equals`, `contains`, `greater_than`, `less_than`, `in` - -### 5. Missing References - -**Error**: `Group 'user_validation' not found` -**Cause**: Referencing a group that doesn't exist -**Fix**: Define the group or check the group name - -### 6. YAML Syntax Errors - -**Error**: `YAML syntax error: found character that cannot start any token` -**Cause**: Invalid YAML syntax -**Fix**: Check YAML formatting, indentation, and special characters - -## Example Valid Flow - -```yaml -flow: UserRegistrationFlow -description: Process new user registration -send: - name: "John Doe" - email: "john@example.com" - password: "securepass123" - -steps: - - type: group - name: user-validation - - - type: condition - condition: - field: email - operator: contains - value: "@" - step: - type: closure - action: send_welcome_email - - - type: nested - steps: - - type: closure - action: hash_password - - type: closure - action: create_user_record - - - type: group - name: user-notifications -``` - -## Invalid Flow Examples - -This section demonstrates common validation errors that the `flowpipe:validate` command can detect. - -## Invalid YAML Structure - -```yaml -flow: InvalidStructureFlow -description: This flow has multiple structural issues -# Missing required steps field -send: - data: "test" -``` - -## Invalid Step Types - -```yaml -flow: InvalidStepTypesFlow -description: This flow has invalid step types -steps: - - type: unsupported_step_type - action: some_action - - type: closure - # Missing required action field - - type: step - # Missing required class field -``` - -## Invalid Condition Steps - -```yaml -flow: InvalidConditionFlow -description: This flow has invalid condition steps -steps: - - type: condition - condition: - field: active - operator: unsupported_operator - value: true - # Missing step to execute - - type: condition - condition: - # Missing required fields - step: - type: closure - action: process_data -``` - -## Invalid Nested Flows - -```yaml -flow: InvalidNestedFlow -description: This flow has invalid nested structure -steps: - - type: nested - # Missing steps array - - type: nested - steps: - - type: closure - # Missing action field - - type: unsupported_type - action: nested_action -``` - -## Invalid Group References - -```yaml -flow: InvalidGroupFlow -description: This flow references non-existent groups -steps: - - type: group - name: non_existent_group - - type: group - # Missing name field -``` - -## Invalid Flow Names - -```yaml -flow: 123-invalid-flow-name -description: Flow names must start with a letter or underscore -steps: - - type: closure - action: test_action -``` - -## Running Validation - -To check for these errors, run: - -```bash -# Validate specific flow -php artisan flowpipe:validate InvalidStructureFlow - -# Validate all flows and see detailed errors -php artisan flowpipe:validate --all - -# Get JSON output for programmatic processing -php artisan flowpipe:validate --all --format=json -``` - -## Common Validation Errors - -1. **Missing required fields**: `flow`, `steps`, `action`, `class`, `name` -2. **Invalid step types**: Only `closure`, `step`, `condition`, `group`, `nested` are supported -3. **Invalid operators**: Only `equals`, `contains`, `greater_than`, `less_than`, `in` are supported -4. **Invalid flow names**: Must start with letter/underscore, contain only alphanumeric, underscore, hyphen -5. **Missing references**: Groups or step classes that don't exist -6. **YAML syntax errors**: Invalid YAML format - -## Best Practices - -1. **Always validate** before deploying flow definitions -2. **Use descriptive names** for flows and steps -3. **Test references** ensure groups and classes exist -4. **Follow naming conventions** for flow names -5. **Structure nested flows** properly with required fields - -## CI/CD Integration - -### Exit Codes - -The validation command returns appropriate exit codes: -- `0`: All flows are valid -- `1`: One or more flows have errors - -### GitHub Actions Example - -```yaml -name: Validate Flow Definitions - -on: [push, pull_request] - -jobs: - validate-flows: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.3' - - - name: Install dependencies - run: composer install --no-dev --optimize-autoloader - - - name: Validate flow definitions - run: php artisan flowpipe:validate --all --format=json -``` - -### Shell Script Example - -```bash -#!/bin/bash -# validate-flows.sh - -echo "Validating flow definitions..." - -if php artisan flowpipe:validate --all; then - echo "โœ… All flows are valid" - exit 0 -else - echo "โŒ Some flows have errors" - exit 1 -fi -``` - -### JSON Output for Processing - -```json -{ - "valid": false, - "flows": [ - { - "name": "UserProcessingFlow", - "valid": true, - "errors": [], - "warnings": [], - "error_count": 0, - "warning_count": 0 - }, - { - "name": "InvalidFlow", - "valid": false, - "errors": [ - "Step 1: Unsupported step type 'invalid_type'", - "Step 2: Closure step missing 'action' field" - ], - "warnings": [], - "error_count": 2, - "warning_count": 0 - } - ], - "summary": { - "total": 2, - "valid": 1, - "invalid": 1, - "errors": 2, - "warnings": 0 - } -} -``` diff --git a/examples/README.md b/examples/README.md deleted file mode 100644 index 3fb0961..0000000 --- a/examples/README.md +++ /dev/null @@ -1,503 +0,0 @@ -# Examples - -This directory contains practical examples of using Laravel Flowpipe in real-world scenarios, with special focus on **Error Handling Strategies**, **Step Groups**, **Nested Flows**, and the new **Enhanced Mermaid Export with Color Coding** features. - -## Directory Structure - -``` -examples/ -โ”œโ”€โ”€ flows/ # Example YAML flow definitions -โ”œโ”€โ”€ groups/ # Example group definitions (YAML) -โ”œโ”€โ”€ steps/ # Example step implementations -โ”œโ”€โ”€ conditions/ # Example condition implementations -โ”œโ”€โ”€ error-handling-comprehensive.php # Comprehensive error handling examples -โ”œโ”€โ”€ advanced-error-handling.php # Advanced error handling patterns -โ”œโ”€โ”€ groups-and-nested-flows.md # Comprehensive guide -โ”œโ”€โ”€ ecommerce-order-processing-groups.php # PHP example -โ”œโ”€โ”€ user-registration-groups.php # PHP example -โ””โ”€โ”€ README.md # This file -``` - -## Featured Examples - -### 1. Comprehensive Error Handling Patterns - -**File**: `error-handling-comprehensive.php` - -This example demonstrates real-world error handling scenarios including: -- **E-commerce Order Processing**: Complete order flow with payment retry, inventory compensation, and email fallback -- **User Registration**: Multi-step registration with service fallbacks and rollback strategies -- **File Processing**: Large file processing with retry mechanisms and cleanup compensation -- **API Integration**: Circuit breaker pattern with primary/secondary API fallback -- **Database Operations**: Saga pattern with automatic compensation - -**Key Features**: -- Retry strategies (exponential backoff, linear backoff, custom logic) -- Fallback mechanisms (default values, cached data, alternative services) -- Compensation patterns (rollback, cleanup, resource release) -- Composite strategies (multi-layered error handling) -- Exception-specific handling - -### 2. Advanced Error Handling Patterns - -**File**: `advanced-error-handling.php` - -Advanced patterns demonstrating: -- Custom retry strategies with conditional logic -- Multi-step workflows with different error handling per step -- Monitoring and logging integration -- Performance-aware error handling -- Production-ready error handling templates - -## Error Handling Strategy Overview - -### Available Strategies - -๐Ÿ”„ **Retry Strategies** -- `exponentialBackoff()` - Exponential backoff with configurable multiplier -- `linearBackoff()` - Linear delay increase -- `withRetryStrategy()` - Custom retry logic -- `fallbackOnException()` - Exception-specific retry - -๐Ÿ›ก๏ธ **Fallback Strategies** -- `withFallback()` - Generic fallback handler -- `fallbackOnException()` - Exception-specific fallback -- Default values, cached data, alternative services - -๐Ÿ”ง **Compensation Strategies** -- `withCompensation()` - Generic compensation handler -- `compensateOnException()` - Exception-specific compensation -- Rollback, cleanup, resource release - -๐ŸŽฏ **Composite Strategies** -- `withCompositeErrorHandler()` - Combine multiple strategies -- `withErrorHandler()` - Use custom error handler -- Multi-layered error handling - -### Real-World Use Cases - -**E-commerce**: -```php -// Order processing with comprehensive error handling -$result = Flowpipe::make() - ->send($orderData) - ->exponentialBackoff(3, 200, 2.0) // Retry payment - ->withFallback(fn($payload, $error) => useAlternativePayment($payload)) - ->withCompensation(fn($payload, $error) => refundPayment($payload)) - ->through($orderSteps) - ->thenReturn(); -``` - -**Microservices**: -```php -// Circuit breaker pattern -$circuitBreaker = CompositeStrategy::make() - ->retry(RetryStrategy::exponentialBackoff(3, 100, 2.0)) - ->fallback(FallbackStrategy::make(fn($payload, $error) => getCachedData($payload))); - -$result = Flowpipe::make() - ->send($serviceRequest) - ->withErrorHandler($circuitBreaker) - ->through($serviceSteps) - ->thenReturn(); -``` - -**Database Transactions**: -```php -// Saga pattern with compensation -$result = Flowpipe::make() - ->send($transactionData) - ->withCompensation(fn($payload, $error) => rollbackAllOperations($payload)) - ->through($transactionSteps) - ->thenReturn(); -``` - -## New Features Examples - -### 1. Flow Validation - -Laravel Flowpipe now includes comprehensive flow validation to ensure your YAML flow definitions are correct: - -```bash -# Validate all flows in the examples directory -php artisan flowpipe:validate --all - -# Validate a specific flow example -php artisan flowpipe:validate --path=flows/user-registration.yaml - -# Get JSON output for CI/CD integration -php artisan flowpipe:validate --all --format=json -``` - -**Key validation features:** -- **Structure validation**: Ensures all required fields are present -- **Step type validation**: Validates that only supported step types are used -- **Reference validation**: Checks that groups and step classes exist -- **Condition validation**: Validates condition operators and structure -- **YAML syntax validation**: Catches YAML formatting errors - -**Example validation output:** -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Flow โ”‚ Status โ”‚ Errors โ”‚ Warnings โ”‚ -โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค -โ”‚ user-registration โ”‚ โœ… Valid โ”‚ 0 โ”‚ 0 โ”‚ -โ”‚ order-processing โ”‚ โœ… Valid โ”‚ 0 โ”‚ 1 โ”‚ -โ”‚ content-moderation โ”‚ โœ… Valid โ”‚ 0 โ”‚ 0 โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - -### 2. Enhanced Mermaid Export with Color Coding - -Laravel Flowpipe now supports rich color coding in Mermaid diagrams for different step types: - -- **Groups**: Blue theme (`๐Ÿ“ฆ Group elements`) -- **Nested Flows**: Light green theme (`๐Ÿ”„ Nested elements`) -- **Conditional Steps**: Orange theme (`โ“ Conditional elements`) -- **Transform Steps**: Pink theme (`๐Ÿ”„ Transform elements`) -- **Validation Steps**: Green theme (`โœ… Validation elements`) -- **Cache Steps**: Yellow theme (`๐Ÿ’พ Cache elements`) -- **Batch Steps**: Purple theme (`๐Ÿ“Š Batch elements`) -- **Retry Steps**: Red theme (`๐Ÿ”„ Retry elements`) - -```bash -# Export a flow with enhanced color coding -php artisan flowpipe:export user-registration --format=mermaid - -# Export a group with colors -php artisan flowpipe:export user-validation --type=group --format=mermaid - -# Export to markdown with embedded colored diagram -php artisan flowpipe:export user-registration --format=md --output=docs/user-registration.md -``` - -### 3. Step Groups and Nested Flows Guide -- **File**: `groups-and-nested-flows.md` -- **Description**: Comprehensive guide with examples showing how to use step groups and nested flows -- **Features**: Basic groups, nested flows, combinations, YAML definitions, real-world examples, color visualization - -### 4. E-commerce Order Processing with Groups -- **PHP Example**: `ecommerce-order-processing-groups.php` -- **YAML Flow**: `flows/ecommerce-order-groups.yaml` -- **Groups Used**: `order-validation`, `inventory-management`, `order-notifications` -- **Description**: Complete e-commerce order processing using step groups and nested flows for payment processing and order creation - -### 5. User Registration with Groups and Nested Flows -- **PHP Example**: `user-registration-groups.php` -- **YAML Flow**: `flows/user-registration-groups.yaml` -- **Groups Used**: `user-validation`, `user-setup`, `user-notifications` -- **Description**: Complete user registration process with password hashing in nested flows, profile creation, and role assignment - -## Available Examples - -### 1. User Registration Flow -- **Flow**: `flows/user-registration.yaml` -- **Groups**: `groups/user-validation.yaml` -- **Steps**: `steps/user-registration/` -- **Description**: Complete user registration process with validation, creation, and email verification - -### 2. Order Processing Flow -- **Flow**: `flows/order-processing.yaml` -- **Groups**: `groups/order-processing.yaml` -- **Steps**: `steps/order-processing/` -- **Conditions**: `conditions/order-processing/` -- **Description**: E-commerce order processing with inventory checks, payment, and fulfillment - -### 3. Content Moderation Flow -- **Flow**: `flows/content-moderation.yaml` -- **Steps**: `steps/content-moderation/` -- **Conditions**: `conditions/content-moderation/` -- **Description**: Automated content moderation with AI analysis and human review - -### 4. Data Processing Pipeline -- **Flow**: `flows/data-processing.yaml` -- **Steps**: `steps/data-processing/` -- **Description**: ETL pipeline for data transformation and analysis - -### 5. Newsletter Campaign Flow -- **Flow**: `flows/newsletter-campaign.yaml` -- **Steps**: `steps/newsletter/` -- **Conditions**: `conditions/newsletter/` -- **Description**: Newsletter campaign management with segmentation and scheduling - -## Step Groups - -The examples demonstrate multiple types of reusable step groups organized by functionality: - -### User-Related Groups - -#### User Validation Group (`groups/user-validation.yaml`) -- Email format validation -- Password strength checking -- Required field validation -- User existence checking -- Terms acceptance validation - -#### User Setup Group (`groups/user-setup.yaml`) -- User ID generation -- Password hashing -- Profile creation -- Default preferences setup -- API key generation - -#### User Notifications Group (`groups/user-notifications.yaml`) -- Welcome email sending -- Email verification dispatch -- Admin notifications -- Registration event logging -- User statistics updates - -### E-commerce Groups - -#### Order Validation Group (`groups/order-validation.yaml`) -- Order structure validation -- Customer information validation -- Item and quantity validation -- Order total verification -- Business rules checking - -#### Inventory Management Group (`groups/inventory-management.yaml`) -- Item availability checking -- Inventory reservation -- Shipping cost calculation -- Inventory level updates -- Inventory reporting - -#### Order Notifications Group (`groups/order-notifications.yaml`) -- Order confirmation emails -- SMS notifications -- Customer account updates -- Warehouse notifications -- Notification event logging - -### General Processing Groups - -#### Order Processing Group (`groups/order-processing.yaml`) -- Order data validation -- Inventory checking and reservation -- Payment processing -- Order record creation -- Confirmation generation - -#### Notifications Group (`groups/notifications.yaml`) -- Welcome email sending -- Verification email dispatch -- SMS notifications -- Event logging -- Admin notifications - -## Nested Flows Examples - -The examples show nested flows used for: - -1. **Payment Processing**: Isolated payment handling with error management -2. **Password Hashing**: Secure password processing with cleanup -3. **Profile Creation**: User profile setup with privacy settings -4. **Role Assignment**: Permission and quota management -5. **Order Creation**: Order record generation with metadata - -## Usage - -### 1. Copy Examples to Your Project - -```bash -# Copy flow definitions -cp examples/flows/*.yaml flow_definitions/ - -# Copy group definitions -cp examples/groups/*.yaml flow_definitions/groups/ - -# Copy step classes -cp -r examples/steps/ app/Flowpipe/Steps/ - -# Copy condition classes -cp -r examples/conditions/ app/Flowpipe/Conditions/ -``` - -### 2. Run PHP Examples - -```bash -# Run e-commerce example -php examples/ecommerce-order-processing-groups.php - -# Run user registration example -php examples/user-registration-groups.php -``` - -### 3. Install Required Dependencies - -Some examples may require additional packages: - -```bash -# For content moderation example -composer require openai-php/client - -# For newsletter example -composer require mailgun/mailgun-php - -# For data processing example -composer require league/csv -``` - -### 4. Configure Examples - -Update the class namespaces in YAML files to match your project structure: - -```yaml -# Before -steps: - - type: closure - action: validate_email - -# After (if using custom steps) -steps: - - type: step - class: App\Flowpipe\Steps\ValidateEmailStep -``` - -### 5. Validate Examples - -Before using the examples, validate them to ensure they're correct: - -```bash -# Validate all example flows -php artisan flowpipe:validate --all - -# Validate specific flows -php artisan flowpipe:validate --path=flows/user-registration.yaml -php artisan flowpipe:validate --path=flows/order-processing.yaml - -# Run validation in CI/CD -php artisan flowpipe:validate --all --format=json > validation-results.json -``` - -**Best practices for validation:** -- Always validate after copying examples to your project -- Run validation in your CI/CD pipeline -- Check validation results before deploying -- Use JSON format for automated processing - -### 6. Export Examples with Enhanced Colors - -```bash -# Export all flows with enhanced colors -php artisan flowpipe:list | tail -n +3 | while read flow; do - php artisan flowpipe:export "$flow" --format=mermaid --output="docs/diagrams/${flow}.mermaid" -done - -# Export groups with color coding -php artisan flowpipe:export user-validation --type=group --format=mermaid -php artisan flowpipe:export order-processing --type=group --format=mermaid -php artisan flowpipe:export notifications --type=group --format=mermaid - -# Generate documentation with embedded colored diagrams -php artisan flowpipe:export user-registration --format=md --output="docs/flows/user-registration.md" -``` - -### 7. Color Visualization Example - -Create a flow that demonstrates all available colors: - -```php -// Define groups for color demonstration -Flowpipe::group('validation-group', [ - fn($data, $next) => $next($data), // Will appear in blue -]); - -$result = Flowpipe::make() - ->send(['test' => 'data']) - ->useGroup('validation-group') // Blue group - ->transform(fn($data) => $data) // Pink transform - ->validate(['test' => 'required']) // Green validation - ->cache('demo-key', 3600) // Yellow cache - ->nested([ // Light green nested - fn($data, $next) => $next(array_merge($data, ['nested' => true])), - ]) - ->batch(100) // Purple batch - ->retry(3) // Red retry - ->thenReturn(); - -// Export this to see all colors: -// php artisan flowpipe:export color-demo --format=mermaid -``` - -## Key Benefits Demonstrated - -### Step Groups -- **Reusability**: Groups can be used across multiple flows -- **Modularity**: Each group handles specific concerns -- **Maintainability**: Easy to update individual components -- **Testing**: Groups can be tested independently - -### Nested Flows -- **Isolation**: Complex logic runs in its own context -- **Security**: Sensitive operations (like password hashing) are contained -- **Organization**: Complex workflows are broken into manageable pieces -- **Error Handling**: Errors in nested flows can be handled appropriately - -## Customization - -### Adapting to Your Needs - -1. **Modify Group Definitions**: Update YAML files to match your business logic -2. **Extend Step Classes**: Add your specific business logic to step implementations -3. **Custom Conditions**: Create conditions that match your application's requirements -4. **Add Error Handling**: Implement proper error handling for your use cases - -### Best Practices - -1. **Start Simple**: Begin with basic groups and gradually add complexity -2. **Test Thoroughly**: Each example includes test patterns you can adapt -3. **Document Changes**: Keep track of modifications you make to examples -4. **Follow Conventions**: Maintain consistent naming and structure -5. **Use Nested Flows Wisely**: Don't overuse nested flows for simple operations - -## Testing Examples - -The examples include testing patterns: - -```php -// Test a group -public function test_user_validation_group() -{ - $result = Flowpipe::make() - ->send($testData) - ->useGroup('user-validation') - ->thenReturn(); - - $this->assertTrue($result['validation_passed']); -} - -// Test a nested flow -public function test_password_processing_nested_flow() -{ - $result = Flowpipe::make() - ->send(['password' => 'test123']) - ->nested([ - fn($data, $next) => $next(['password_hash' => password_hash($data['password'], PASSWORD_DEFAULT)]), - ]) - ->thenReturn(); - - $this->assertArrayHasKey('password_hash', $result); -} -``` - -## Contributing Examples - -If you have a useful flow example to share: - -1. Create a new example file with clear documentation -2. Include both PHP and YAML versions when applicable -3. Add comprehensive comments explaining the workflow -4. Include testing examples -5. Submit a pull request - -## Support - -For questions about these examples: - -1. Check the main documentation in `docs/` -2. Review the comprehensive guide in `groups-and-nested-flows.md` -3. Review the test cases for implementation details -4. Open an issue on the GitHub repository -5. Join the community discussions diff --git a/examples/advanced-error-handling.php b/examples/advanced-error-handling.php deleted file mode 100644 index 174d598..0000000 --- a/examples/advanced-error-handling.php +++ /dev/null @@ -1,266 +0,0 @@ - 'success']); - } - - public static function reset(): void - { - self::$callCount = 0; - } -} - -final class PaymentService -{ - public static function process(array $data): array - { - if (rand(1, 10) > 7) { - throw new RuntimeException('Payment declined'); - } - - return array_merge($data, ['payment_status' => 'paid']); - } -} - -echo "=== Advanced Error Handling Strategies Demo ===\n\n"; - -// Example 1: Retry Strategy with Exponential Backoff -echo "1. Retry Strategy with Exponential Backoff\n"; -echo "-------------------------------------------\n"; - -try { - UnreliableApiService::reset(); - - $result = Flowpipe::make() - ->send(['user_id' => 123, 'action' => 'fetch_profile']) - ->exponentialBackoff(5, 100, 2.0) // 5 attempts, 100ms base delay, 2x multiplier - ->through([ - fn ($data, $next) => $next(UnreliableApiService::call($data)), - ]) - ->thenReturn(); - - echo 'โœ“ Success: '.json_encode($result)."\n"; -} catch (Exception $e) { - echo 'โœ— Failed: '.$e->getMessage()."\n"; -} - -echo "\n"; - -// Example 2: Fallback Strategy -echo "2. Fallback Strategy\n"; -echo "--------------------\n"; - -try { - $result = Flowpipe::make() - ->send(['user_id' => 123, 'amount' => 100]) - ->withFallback(function ($payload, $error) { - echo 'Fallback triggered: '.$error->getMessage()."\n"; - - return array_merge($payload, ['payment_status' => 'pending', 'fallback_used' => true]); - }) - ->through([ - fn ($data, $next) => $next(PaymentService::process($data)), - ]) - ->thenReturn(); - - echo 'โœ“ Result: '.json_encode($result)."\n"; -} catch (Exception $e) { - echo 'โœ— Failed: '.$e->getMessage()."\n"; -} - -echo "\n"; - -// Example 3: Compensation Strategy -echo "3. Compensation Strategy\n"; -echo "------------------------\n"; - -try { - $result = Flowpipe::make() - ->send(['order_id' => 456, 'items' => ['item1', 'item2']]) - ->withCompensation(function ($payload, $error, $context) { - echo 'Compensation triggered: '.$error->getMessage()."\n"; - echo 'Rolling back order: '.$payload['order_id']."\n"; - - return array_merge($payload, ['compensation_applied' => true, 'order_status' => 'cancelled']); - }) - ->through([ - function ($data, $next) { - // Simulate order processing failure - if (rand(1, 2) === 1) { - throw new RuntimeException('Inventory not available'); - } - - return $next(array_merge($data, ['order_status' => 'confirmed'])); - }, - ]) - ->thenReturn(); - - echo 'โœ“ Result: '.json_encode($result)."\n"; -} catch (Exception $e) { - echo 'โœ— Failed: '.$e->getMessage()."\n"; -} - -echo "\n"; - -// Example 4: Composite Strategy (Retry + Fallback) -echo "4. Composite Strategy (Retry + Fallback)\n"; -echo "----------------------------------------\n"; - -try { - $compositeStrategy = CompositeStrategy::make() - ->retry(RetryStrategy::exponentialBackoff(3, 50, 2.0)) - ->fallback(FallbackStrategy::withDefault(['status' => 'cached', 'fallback' => true])); - - $result = Flowpipe::make() - ->send(['request' => 'user_data']) - ->withErrorHandler($compositeStrategy) - ->through([ - function ($data, $next) { - // Simulate service that always fails - throw new RuntimeException('Service unavailable'); - }, - ]) - ->thenReturn(); - - echo 'โœ“ Result: '.json_encode($result)."\n"; -} catch (Exception $e) { - echo 'โœ— Failed: '.$e->getMessage()."\n"; -} - -echo "\n"; - -// Example 5: Exception-specific Strategies -echo "5. Exception-specific Strategies\n"; -echo "--------------------------------\n"; - -final class ValidationException extends Exception {} -final class NetworkException extends Exception {} - -try { - $result = Flowpipe::make() - ->send(['email' => 'invalid-email']) - ->fallbackOnException(ValidationException::class, function ($payload, $error) { - echo "Validation fallback: Using default email\n"; - - return array_merge($payload, ['email' => 'default@example.com']); - }) - ->compensateOnException(NetworkException::class, function ($payload, $error, $context) { - echo "Network compensation: Queuing for later\n"; - - return array_merge($payload, ['queued' => true]); - }) - ->through([ - function ($data, $next) { - if (! filter_var($data['email'], FILTER_VALIDATE_EMAIL)) { - throw new ValidationException('Invalid email format'); - } - - return $next($data); - }, - ]) - ->thenReturn(); - - echo 'โœ“ Result: '.json_encode($result)."\n"; -} catch (Exception $e) { - echo 'โœ— Failed: '.$e->getMessage()."\n"; -} - -echo "\n"; - -// Example 6: Complex Workflow with Multiple Error Handling -echo "6. Complex Workflow with Multiple Error Handling\n"; -echo "------------------------------------------------\n"; - -try { - $result = Flowpipe::make() - ->send(['user_id' => 789, 'order_total' => 99.99]) - - // Step 1: Validate with fallback - ->withFallback(function ($payload, $error) { - echo "Validation fallback: Using guest user\n"; - - return array_merge($payload, ['user_id' => 'guest', 'guest_order' => true]); - }) - ->through([ - function ($data, $next) { - if ($data['user_id'] === 999) { - throw new RuntimeException('User not found'); - } - - return $next(array_merge($data, ['user_validated' => true])); - }, - ]) - - // Step 2: Process payment with retry - ->exponentialBackoff(3, 100, 2.0) - ->through([ - function ($data, $next) { - if (rand(1, 10) > 8) { - throw new RuntimeException('Payment gateway timeout'); - } - - return $next(array_merge($data, ['payment_processed' => true])); - }, - ]) - - // Step 3: Fulfill order with compensation - ->withCompensation(function ($payload, $error, $context) { - echo "Order compensation: Refunding payment\n"; - - return array_merge($payload, ['refund_issued' => true, 'order_cancelled' => true]); - }) - ->through([ - function ($data, $next) { - if (rand(1, 10) > 9) { - throw new RuntimeException('Fulfillment center unavailable'); - } - - return $next(array_merge($data, ['order_fulfilled' => true])); - }, - ]) - - ->thenReturn(); - - echo 'โœ“ Final Result: '.json_encode($result)."\n"; -} catch (Exception $e) { - echo 'โœ— Workflow Failed: '.$e->getMessage()."\n"; -} - -echo "\n=== Error Handling Benefits ===\n"; -echo "โœ“ Retry: Automatic recovery from transient failures\n"; -echo "โœ“ Fallback: Graceful degradation when services are unavailable\n"; -echo "โœ“ Compensation: Rollback operations to maintain data consistency\n"; -echo "โœ“ Composite: Combine multiple strategies for comprehensive error handling\n"; -echo "โœ“ Exception-specific: Handle different types of errors appropriately\n"; -echo "โœ“ Configurable: Customize delay, attempts, and conditions\n"; -echo "โœ“ Maintainable: Clear separation of business logic and error handling\n"; diff --git a/examples/color-demo-example.php b/examples/color-demo-example.php deleted file mode 100644 index 939f280..0000000 --- a/examples/color-demo-example.php +++ /dev/null @@ -1,103 +0,0 @@ - $next(array_merge($data, ['email_validated' => true])), - fn ($data, $next) => $next(array_merge($data, ['name_validated' => true])), -]); - -Flowpipe::group('processing-group', [ - fn ($data, $next) => $next(array_merge($data, ['processed' => true])), - fn ($data, $next) => $next(array_merge($data, ['timestamp' => now()])), -]); - -// Create a comprehensive flow that demonstrates all color types -$result = Flowpipe::make() - ->send(['name' => 'John Doe', 'email' => 'john@example.com']) - - // Blue group - User validation - ->useGroup('validation-group') - - // Pink transform - Data transformation - ->transform(fn ($data) => array_merge($data, ['name' => mb_strtoupper($data['name'])])) - - // Green validation - Laravel validation - ->validate([ - 'name' => 'required|string|min:2', - 'email' => 'required|email', - ]) - - // Yellow cache - Cache the validated data - ->cache('user-data-'.md5(serialize(['name' => 'John Doe', 'email' => 'john@example.com'])), 3600) - - // Light green nested flow - Complex password processing - ->nested([ - fn ($data, $next) => $next(array_merge($data, ['password_hash' => password_hash('defaultpassword', PASSWORD_DEFAULT)])), - fn ($data, $next) => $next(array_merge($data, ['password_verified' => true])), - fn ($data, $next) => $next(array_merge($data, ['security_level' => 'high'])), - ]) - - // Blue group - Additional processing - ->useGroup('processing-group') - - // Purple batch - Batch process the data - ->batch(100, true) - - // Red retry - Retry logic for reliability - ->retry(3, 100) - - // Final processing - ->through([ - fn ($data, $next) => $next(array_merge($data, ['completed' => true])), - ]) - - ->thenReturn(); - -// Display results -echo "=== Enhanced Color Demo Results ===\n"; -echo "User data processed successfully!\n"; -echo 'Name: '.$result['name']."\n"; -echo 'Email: '.$result['email']."\n"; -echo 'Email Validated: '.($result['email_validated'] ? 'Yes' : 'No')."\n"; -echo 'Name Validated: '.($result['name_validated'] ? 'Yes' : 'No')."\n"; -echo 'Password Hash: '.mb_substr($result['password_hash'], 0, 20)."...\n"; -echo 'Security Level: '.$result['security_level']."\n"; -echo 'Processed: '.($result['processed'] ? 'Yes' : 'No')."\n"; -echo 'Completed: '.($result['completed'] ? 'Yes' : 'No')."\n"; - -echo "\n=== Export Commands ===\n"; -echo "To see the enhanced color-coded Mermaid diagram, run:\n"; -echo "php artisan flowpipe:export color-demo --format=mermaid\n"; -echo "php artisan flowpipe:export validation-group --type=group --format=mermaid\n"; -echo "php artisan flowpipe:export processing-group --type=group --format=mermaid\n"; - -echo "\n=== Color Legend ===\n"; -echo "๐Ÿ“ฆ Blue: Groups (validation-group, processing-group)\n"; -echo "๐Ÿ”„ Pink: Transform steps\n"; -echo "โœ… Green: Validation steps\n"; -echo "๐Ÿ’พ Yellow: Cache steps\n"; -echo "๐Ÿ”„ Light Green: Nested flows\n"; -echo "๐Ÿ“Š Purple: Batch steps\n"; -echo "๐Ÿ”„ Red: Retry steps\n"; - -echo "\n=== Example Mermaid Output ===\n"; -echo "The generated Mermaid diagram will show:\n"; -echo "- Blue boxes for groups\n"; -echo "- Pink box for transform step\n"; -echo "- Green box for validation step\n"; -echo "- Yellow box for cache step\n"; -echo "- Light green box for nested flow\n"; -echo "- Purple box for batch step\n"; -echo "- Red box for retry step\n"; -echo "- All connected with arrows showing the flow\n"; diff --git a/examples/complete-ecommerce-workflow.md b/examples/complete-ecommerce-workflow.md deleted file mode 100644 index bbf2512..0000000 --- a/examples/complete-ecommerce-workflow.md +++ /dev/null @@ -1,365 +0,0 @@ -# Exemple complet : Workflow de commande e-commerce - -Cet exemple montre comment utiliser Laravel Flowpipe pour crรฉer un workflow complet de traitement de commande e-commerce avec des groupes d'รฉtapes et des flux imbriquรฉs. - -## Configuration initiale - -```php - 'numeric', - 'items' => 'required', - 'total' => 'numeric', - ]), - new AuthenticationStep(['create_order']), - new LoggingStep('info', 'Order validation completed'), -]); - -Flowpipe::group('payment-processing', [ - new ApiCallStep('/api/payments/validate', 'POST'), - new DatabaseStep('insert', 'payments'), - new CacheStep('payment_status', 300), - new LoggingStep('info', 'Payment processed'), -]); - -Flowpipe::group('inventory-management', [ - new DatabaseStep('update', 'inventory'), - new ApiCallStep('/api/inventory/reserve', 'POST'), - new CacheStep('inventory_status', 1800), - new LoggingStep('info', 'Inventory updated'), -]); - -Flowpipe::group('order-fulfillment', [ - new DatabaseStep('insert', 'orders'), - new ApiCallStep('/api/shipping/create', 'POST'), - new FileOperationStep('create', '/storage/invoices'), - new LoggingStep('info', 'Order fulfilled'), -]); - -Flowpipe::group('customer-notifications', [ - new NotificationStep('email', ['order-confirmation']), - new NotificationStep('sms', ['order-update']), - new ApiCallStep('/api/external/notify', 'POST'), - new LoggingStep('info', 'Customer notified'), -]); - -Flowpipe::group('post-order-processing', [ - new CacheStep('order_analytics', 3600), - new ApiCallStep('/api/analytics/track', 'POST'), - new DatabaseStep('update', 'customer_stats'), - new LoggingStep('info', 'Post-order processing completed'), -]); -``` - -## Workflow principal - -```php -send($orderData) - - // ร‰tape 1 : Validation initiale - ->useGroup('order-validation') - - // ร‰tape 2 : Traitement des paiements et gestion des erreurs - ->through([ - new ErrorHandlingStep(false, [ - function($error, $payload) { - Log::error('Payment processing error', [ - 'error' => $error, - 'order_id' => $payload['order_id'] ?? null - ]); - } - ]), - ]) - ->useGroup('payment-processing') - - // ร‰tape 3 : Gestion de l'inventaire avec workflow imbriquรฉ - ->nested([ - // Vรฉrification de la disponibilitรฉ - new ApiCallStep('/api/inventory/check', 'POST'), - - // Rรฉservation conditionnelle - new class implements \Grazulex\LaravelFlowpipe\Contracts\FlowStep { - public function handle(mixed $payload, \Closure $next): mixed - { - if (!isset($payload['inventory_available']) || !$payload['inventory_available']) { - return $next([ - ...$payload, - 'error' => 'Insufficient inventory', - 'order_status' => 'cancelled' - ]); - } - - return $next([ - ...$payload, - 'inventory_reserved' => true - ]); - } - }, - ]) - ->useGroup('inventory-management') - - // ร‰tape 4 : Traitement conditionnel selon le statut - ->through([ - new class implements \Grazulex\LaravelFlowpipe\Contracts\FlowStep { - public function handle(mixed $payload, \Closure $next): mixed - { - if (isset($payload['order_status']) && $payload['order_status'] === 'cancelled') { - // Workflow d'annulation - return Flowpipe::make() - ->send($payload) - ->through([ - new DatabaseStep('update', 'orders', ['status' => 'cancelled']), - new NotificationStep('email', ['order-cancelled']), - new LoggingStep('warning', 'Order cancelled'), - ]) - ->thenReturn(); - } - - return $next($payload); - } - }, - ]) - - // ร‰tape 5 : Finalisation de la commande - ->useGroup('order-fulfillment') - - // ร‰tape 6 : Notifications client avec workflow imbriquรฉ - ->nested([ - // Prรฉparation des donnรฉes de notification - new class implements \Grazulex\LaravelFlowpipe\Contracts\FlowStep { - public function handle(mixed $payload, \Closure $next): mixed - { - $notificationData = [ - 'customer_name' => $payload['customer_name'] ?? 'Customer', - 'order_number' => $payload['order_number'] ?? 'N/A', - 'estimated_delivery' => date('Y-m-d', strtotime('+3 days')), - ]; - - return $next([ - ...$payload, - 'notification_data' => $notificationData - ]); - } - }, - - // Envoi des notifications - new NotificationStep('email', ['customer']), - new NotificationStep('sms', ['customer']), - ]) - ->useGroup('customer-notifications') - - // ร‰tape 7 : Traitement post-commande - ->useGroup('post-order-processing') - - // ร‰tape 8 : Nettoyage et finalisation - ->through([ - new class implements \Grazulex\LaravelFlowpipe\Contracts\FlowStep { - public function handle(mixed $payload, \Closure $next): mixed - { - // Nettoyage des donnรฉes temporaires - $cleanPayload = array_diff_key($payload, [ - 'temporary_data' => null, - 'cache_keys' => null, - ]); - - return $next([ - ...$cleanPayload, - 'processing_completed' => true, - 'completed_at' => date('Y-m-d H:i:s'), - ]); - } - }, - new LoggingStep('info', 'Order processing completed successfully'), - ]) - - ->thenReturn(); - } -} -``` - -## Utilisation dans un contrรดleur - -```php -validate([ - 'customer_id' => 'required|integer', - 'items' => 'required|array', - 'items.*.product_id' => 'required|integer', - 'items.*.quantity' => 'required|integer|min:1', - 'items.*.price' => 'required|numeric|min:0', - 'total' => 'required|numeric|min:0', - 'payment_method' => 'required|string', - ]); - - $result = $this->orderService->processOrder($orderData); - - if (isset($result['error'])) { - return response()->json([ - 'success' => false, - 'message' => $result['error'], - 'order_status' => $result['order_status'] ?? 'failed' - ], 400); - } - - return response()->json([ - 'success' => true, - 'message' => 'Order processed successfully', - 'order_id' => $result['order_id'] ?? null, - 'order_status' => $result['order_status'] ?? 'confirmed', - 'estimated_delivery' => $result['notification_data']['estimated_delivery'] ?? null, - ]); - } -} -``` - -## Test du workflow - -```php - 1, - 'items' => [ - ['product_id' => 1, 'quantity' => 2, 'price' => 25.99], - ['product_id' => 2, 'quantity' => 1, 'price' => 15.50], - ], - 'total' => 67.48, - 'payment_method' => 'credit_card', - ]; - - $service = new OrderProcessingService(); - $result = $service->processOrder($orderData); - - expect($result) - ->toHaveKey('processing_completed', true) - ->toHaveKey('order_status', 'confirmed') - ->toHaveKey('completed_at') - ->not->toHaveKey('error'); - } - - /** @test */ - public function it_handles_insufficient_inventory() - { - $orderData = [ - 'customer_id' => 1, - 'items' => [ - ['product_id' => 999, 'quantity' => 100, 'price' => 25.99], - ], - 'total' => 2599.00, - 'payment_method' => 'credit_card', - 'inventory_available' => false, // Simulate insufficient inventory - ]; - - $service = new OrderProcessingService(); - $result = $service->processOrder($orderData); - - expect($result) - ->toHaveKey('order_status', 'cancelled') - ->toHaveKey('error', 'Insufficient inventory'); - } - - /** @test */ - public function it_logs_all_processing_steps() - { - $orderData = [ - 'customer_id' => 1, - 'items' => [['product_id' => 1, 'quantity' => 1, 'price' => 10.00]], - 'total' => 10.00, - 'payment_method' => 'credit_card', - ]; - - $service = new OrderProcessingService(); - $result = $service->processOrder($orderData); - - // Vรฉrifier que toutes les รฉtapes de logging ont รฉtรฉ exรฉcutรฉes - expect($result) - ->toHaveKey('logged', true) - ->toHaveKey('processing_completed', true); - } -} -``` - -## Configuration avancรฉe avec middleware - -```php - $executionTime, - 'memory_usage' => memory_get_usage(true), - 'payload_size' => strlen(json_encode($payload)) - ]); - - return $result; - } -} - -// Ajout du middleware ร  tous les groupes -Flowpipe::group('monitored-validation', [ - new LoggingMiddleware(), - new DataValidationStep([ - 'customer_id' => 'numeric', - 'total' => 'numeric', - ]), -]); -``` - -## Rรฉsumรฉ des fonctionnalitรฉs utilisรฉes - -1. **Groupes d'รฉtapes** : Rรฉutilisation de workflows communs -2. **Flux imbriquรฉs** : Traitement conditionnel et sous-workflows -3. **Gestion d'erreurs** : Capture et traitement des erreurs -4. **ร‰tapes personnalisรฉes** : Logique mรฉtier spรฉcifique -5. **Logging** : Traรงabilitรฉ complรจte du workflow -6. **Mise en cache** : Optimisation des performances -7. **Notifications** : Communication avec les clients -8. **Validation** : Contrรดle de la qualitรฉ des donnรฉes - -Cet exemple montre la puissance et la flexibilitรฉ de Laravel Flowpipe pour crรฉer des workflows complexes, maintenables et testables. diff --git a/examples/conditions/user-registration/EmailVerificationEnabledCondition.php b/examples/conditions/user-registration/EmailVerificationEnabledCondition.php deleted file mode 100644 index fe6eaf6..0000000 --- a/examples/conditions/user-registration/EmailVerificationEnabledCondition.php +++ /dev/null @@ -1,23 +0,0 @@ -get('user_already_verified', false); - - return $emailVerificationEnabled && ! $userAlreadyVerified; - } -} diff --git a/examples/conditions/user-registration/WelcomeEmailEnabledCondition.php b/examples/conditions/user-registration/WelcomeEmailEnabledCondition.php deleted file mode 100644 index ce0530b..0000000 --- a/examples/conditions/user-registration/WelcomeEmailEnabledCondition.php +++ /dev/null @@ -1,26 +0,0 @@ -get('validated_data', []); - $marketingOptIn = $validatedData['marketing_opt_in'] ?? true; - - // Check if user registration was successful - $userCreated = $context->get('user_created', false); - - return $welcomeEmailEnabled && $marketingOptIn && $userCreated; - } -} diff --git a/examples/ecommerce-complete.yaml b/examples/ecommerce-complete.yaml deleted file mode 100644 index 501e346..0000000 --- a/examples/ecommerce-complete.yaml +++ /dev/null @@ -1,73 +0,0 @@ -# Example: Complete E-commerce Flow with Groups and Nested Flows -# File: flow_definitions/ecommerce-complete.yaml - -flow: EcommerceCompleteFlow -description: Complete e-commerce workflow using groups and nested flows - -send: - customer_id: 123 - items: - - product_id: 1 - quantity: 2 - price: 25.99 - - product_id: 2 - quantity: 1 - price: 15.50 - total: 67.48 - payment_method: credit_card - -steps: - # Step 1: Use validation group - - type: group - name: user-validation - - # Step 2: Nested flow for complex payment processing - - type: nested - description: Payment processing with fraud detection - steps: - - type: step - class: CheckFraudStep - - type: step - class: ProcessPaymentStep - - type: conditional - condition: - field: payment_status - operator: equals - value: failed - then: - - type: step - class: HandlePaymentFailureStep - else: - - type: step - class: ConfirmPaymentStep - - # Step 3: Use order processing group - - type: group - name: order-processing - - # Step 4: Nested flow for shipping - - type: nested - description: Shipping and fulfillment - steps: - - type: step - class: CalculateShippingStep - - type: step - class: SelectCarrierStep - - type: step - class: GenerateShippingLabelStep - - # Step 5: Use notifications group - - type: group - name: notifications - - # Step 6: Final conditional step - - type: conditional - condition: - field: order_status - operator: equals - value: completed - then: - - type: step - class: UpdateAnalyticsStep - - type: step - class: ScheduleFollowUpStep diff --git a/examples/ecommerce-order-processing-groups.php b/examples/ecommerce-order-processing-groups.php deleted file mode 100644 index bf0d958..0000000 --- a/examples/ecommerce-order-processing-groups.php +++ /dev/null @@ -1,178 +0,0 @@ - $next( - isset($order['items']) && count($order['items']) > 0 - ? $order - : throw new InvalidArgumentException('Order must have items') - ), - - // Validate customer information - fn ($order, $next) => $next( - isset($order['customer']) && ! empty($order['customer']['email']) - ? $order - : throw new InvalidArgumentException('Customer email required') - ), - - // Validate order total - fn ($order, $next) => $next( - isset($order['total']) && $order['total'] > 0 - ? $order - : throw new InvalidArgumentException('Order total must be greater than 0') - ), -]); - -Flowpipe::group('inventory-management', [ - // Check item availability - fn ($order, $next) => $next(array_merge($order, [ - 'inventory_checked' => true, - 'items_available' => true, // Simplified for example - ])), - - // Reserve items - fn ($order, $next) => $next(array_merge($order, [ - 'items_reserved' => true, - 'reservation_id' => 'res_'.uniqid(), - ])), -]); - -Flowpipe::group('order-notifications', [ - // Send customer confirmation - fn ($order, $next) => $next(array_merge($order, [ - 'customer_notified' => true, - 'confirmation_email_sent' => true, - ])), - - // Notify fulfillment team - fn ($order, $next) => $next(array_merge($order, [ - 'fulfillment_notified' => true, - 'fulfillment_ticket' => 'ticket_'.uniqid(), - ])), -]); - -// Sample order data -$orderData = [ - 'items' => [ - ['name' => 'Product A', 'price' => 25.99, 'quantity' => 2], - ['name' => 'Product B', 'price' => 15.50, 'quantity' => 1], - ], - 'customer' => [ - 'name' => 'John Doe', - 'email' => 'john@example.com', - 'address' => '123 Main St, City, Country', - ], - 'total' => 67.48, -]; - -echo "=== E-commerce Order Processing Example ===\n\n"; -echo "Initial Order Data:\n"; -print_r($orderData); -echo "\n"; - -// Process the order using groups and nested flows -try { - $result = Flowpipe::make() - ->send($orderData) - - // Step 1: Validate order - ->useGroup('order-validation') - - // Step 2: Check and reserve inventory - ->useGroup('inventory-management') - - // Step 3: Process payment in nested flow (isolated) - ->nested([ - // Payment processing sub-flow - fn ($order, $next) => $next(array_merge($order, [ - 'payment_id' => 'pay_'.uniqid(), - 'payment_method' => 'credit_card', - ])), - - fn ($order, $next) => $next(array_merge($order, [ - 'payment_status' => 'processed', - 'payment_date' => date('Y-m-d H:i:s'), - ])), - - fn ($order, $next) => $next(array_merge($order, [ - 'transaction_fee' => round($order['total'] * 0.029, 2), // 2.9% fee - ])), - ]) - - // Step 4: Create order record in nested flow - ->nested([ - // Order creation sub-flow - fn ($order, $next) => $next(array_merge($order, [ - 'order_id' => 'order_'.uniqid(), - 'order_number' => 'ORD-'.date('Ymd').'-'.rand(1000, 9999), - ])), - - fn ($order, $next) => $next(array_merge($order, [ - 'status' => 'confirmed', - 'created_at' => date('Y-m-d H:i:s'), - ])), - - fn ($order, $next) => $next(array_merge($order, [ - 'estimated_delivery' => date('Y-m-d', strtotime('+5 days')), - ])), - ]) - - // Step 5: Send notifications - ->useGroup('order-notifications') - - // Step 6: Final processing - ->through([ - fn ($order, $next) => $next(array_merge($order, [ - 'processing_completed' => true, - 'completed_at' => date('Y-m-d H:i:s'), - ])), - ]) - - ->thenReturn(); - - echo "Order processed successfully!\n\n"; - echo "Final Order Data:\n"; - print_r($result); - - echo "\n=== Order Summary ===\n"; - echo 'Order ID: '.$result['order_id']."\n"; - echo 'Order Number: '.$result['order_number']."\n"; - echo 'Status: '.$result['status']."\n"; - echo 'Payment ID: '.$result['payment_id']."\n"; - echo 'Payment Status: '.$result['payment_status']."\n"; - echo 'Total: $'.number_format($result['total'], 2)."\n"; - echo 'Transaction Fee: $'.number_format($result['transaction_fee'], 2)."\n"; - echo 'Estimated Delivery: '.$result['estimated_delivery']."\n"; - echo 'Customer Notified: '.($result['customer_notified'] ? 'Yes' : 'No')."\n"; - echo 'Fulfillment Notified: '.($result['fulfillment_notified'] ? 'Yes' : 'No')."\n"; - -} catch (Exception $e) { - echo 'Error processing order: '.$e->getMessage()."\n"; -} - -echo "\n=== Groups Information ===\n"; -echo "Available Groups:\n"; -foreach (Flowpipe::getGroups() as $groupName => $steps) { - echo "- $groupName (".count($steps)." steps)\n"; -} - -echo "\n=== Workflow Benefits ===\n"; -echo "โœ“ Modular: Each group handles specific concerns\n"; -echo "โœ“ Reusable: Groups can be used in different flows\n"; -echo "โœ“ Isolated: Nested flows provide encapsulation\n"; -echo "โœ“ Maintainable: Easy to update individual components\n"; -echo "โœ“ Testable: Each group and nested flow can be tested independently\n"; diff --git a/examples/ecommerce-order-processing.md b/examples/ecommerce-order-processing.md deleted file mode 100644 index 40ba3cf..0000000 --- a/examples/ecommerce-order-processing.md +++ /dev/null @@ -1,421 +0,0 @@ -# E-commerce Order Processing with Step Groups - -This example demonstrates how to use step groups and nested flows to create a modular, maintainable e-commerce order processing system. - -## Overview - -The order processing flow includes: -1. **Order Validation Group** - Validate order data, inventory, and customer -2. **Payment Processing Group** - Handle payment authorization and capture -3. **Fulfillment Group** - Process shipping and inventory updates -4. **Notification Group** - Send confirmations and updates - -## Flow Definition with Groups - -```php -send($orderData) - ->through([ - 'order-validation', - 'payment-processing', - 'fulfillment', - 'notifications', - ]) - ->thenReturn(); -``` - -## Usage Examples - -### Basic Order Processing - -```php - 123, - 'items' => [ - ['product_id' => 1, 'quantity' => 2, 'price' => 29.99], - ['product_id' => 2, 'quantity' => 1, 'price' => 49.99], - ], - 'shipping_address' => [ - 'street' => '123 Main St', - 'city' => 'New York', - 'zip' => '10001', - ], - 'payment_method' => 'credit_card', - 'payment_token' => 'tok_123456789', -]; - -$result = Flowpipe::make() - ->send($orderData) - ->through([ - 'order-validation', - 'payment-processing', - 'fulfillment', - 'notifications', - ]) - ->thenReturn(); - -if ($result['success']) { - echo "Order #{$result['order_id']} processed successfully!"; -} else { - echo "Order processing failed: " . $result['error']; -} -``` - -### Advanced Processing with Nested Flows - -```php -send($orderData) - ->useGroup('order-validation') - ->nested([ - // Complex payment processing with multiple options - DetectPaymentMethodStep::class, - ClosureStep::make(function ($payload, $next) { - // Conditional payment processing based on method - $paymentFlow = match ($payload['payment_method']) { - 'credit_card' => ['ProcessCreditCardStep', 'ValidateCreditCardStep'], - 'paypal' => ['ProcessPayPalStep', 'ValidatePayPalStep'], - 'apple_pay' => ['ProcessApplePayStep', 'ValidateApplePayStep'], - default => throw new Exception('Unsupported payment method'), - }; - - $result = Flowpipe::make() - ->send($payload) - ->through($paymentFlow) - ->thenReturn(); - - return $next($result); - }), - ]) - ->useGroup('fulfillment') - ->useGroup('notifications') - ->thenReturn(); -``` - -### Conditional Processing with Groups - -```php -send($orderData) - ->useGroup('order-validation') - ->useGroup('payment-processing') - ->through([ - // Conditional fulfillment based on order type - ConditionalStep::when( - new IsDigitalOrderCondition(), - new GroupStep('digital-fulfillment') - ), - ConditionalStep::when( - new IsPhysicalOrderCondition(), - new GroupStep('physical-fulfillment') - ), - ]) - ->useGroup('notifications') - ->thenReturn(); -``` - -## Step Implementations - -### Order Validation Steps - -```php -getAvailableStock($item['product_id']); - - if ($availableStock < $item['quantity']) { - throw new \Exception("Insufficient stock for product {$item['product_id']}"); - } - } - - $payload['validation_status'] = 'inventory_validated'; - - return $next($payload); - } - - private function getAvailableStock(int $productId): int - { - // Simulate database lookup - return rand(1, 100); - } -} -``` - -### Payment Processing Steps - -```php -authorizePayment( - $payload['payment_token'], - $payload['total_amount'] ?? $this->calculateTotal($payload['items']) - ); - - if (!$authResult['success']) { - throw new \Exception("Payment authorization failed: {$authResult['error']}"); - } - - $payload['payment_authorization'] = $authResult['authorization_code']; - $payload['payment_status'] = 'authorized'; - - return $next($payload); - } - - private function authorizePayment(string $token, float $amount): array - { - // Simulate payment gateway call - return [ - 'success' => rand(0, 1) === 1, - 'authorization_code' => 'AUTH_' . uniqid(), - 'error' => 'Insufficient funds', - ]; - } - - private function calculateTotal(array $items): float - { - return array_sum(array_map( - fn($item) => $item['price'] * $item['quantity'], - $items - )); - } -} -``` - -## Testing with Groups - -```php - 123, - 'items' => [ - ['product_id' => 1, 'quantity' => 2, 'price' => 29.99], - ], - 'payment_method' => 'credit_card', - 'payment_token' => 'tok_123456789', - ]; - - $result = Flowpipe::make($tracer) - ->send($orderData) - ->through([ - 'order-validation', - 'payment-processing', - ]) - ->thenReturn(); - - // Verify execution - $this->assertTrue($result['success']); - $this->assertArrayHasKey('order_id', $result); - - // Verify tracing - $steps = $tracer->steps(); - $this->assertContains('GroupStep', $steps); - $this->assertEquals(2, $tracer->count()); - } - - public function test_can_use_nested_flows() - { - $result = Flowpipe::make() - ->send(['data' => 'test']) - ->nested([ - ClosureStep::make(fn($payload, $next) => $next([ - ...$payload, - 'processed' => true, - ])), - ]) - ->through([ - ClosureStep::make(fn($payload, $next) => $next([ - ...$payload, - 'completed' => true, - ])), - ]) - ->thenReturn(); - - $this->assertTrue($result['processed']); - $this->assertTrue($result['completed']); - } -} -``` - -## Configuration - -### Group Configuration File - -You can define groups in a configuration file: - -```php -// config/flowpipe_groups.php -return [ - 'order-validation' => [ - 'ValidateOrderDataStep', - 'CheckInventoryStep', - 'ValidateCustomerStep', - ], - - 'payment-processing' => [ - 'AuthorizePaymentStep', - 'CapturePaymentStep', - ], - - 'fulfillment' => [ - 'ReserveInventoryStep', - 'CreateShipmentStep', - 'UpdateInventoryStep', - ], -]; -``` - -### Loading Groups from Configuration - -```php - $steps) { - Flowpipe::group($name, $steps); -} -``` - -## Benefits of Using Step Groups - -1. **Modularity**: Related steps are grouped together -2. **Reusability**: Groups can be used across different flows -3. **Maintainability**: Changes to a group affect all flows using it -4. **Testing**: Groups can be tested independently -5. **Documentation**: Clear separation of concerns - -This approach makes your Laravel Flowpipe workflows more organized, maintainable, and easier to understand. diff --git a/examples/error-handling-comprehensive.php b/examples/error-handling-comprehensive.php deleted file mode 100644 index d46e8b2..0000000 --- a/examples/error-handling-comprehensive.php +++ /dev/null @@ -1,541 +0,0 @@ - 'pay_'.uniqid(), 'charged' => true]); - } - - public static function refund(string $paymentId): array - { - return ['refund_id' => 'ref_'.uniqid(), 'refunded' => true]; - } - - public static function reset(): void - { - self::$attempts = 0; - } -} - -final class InventoryService -{ - public static function reserve(array $items): array - { - if (rand(1, 10) > 7) { - throw new InventoryException('Insufficient inventory'); - } - - return ['reservation_id' => 'res_'.uniqid(), 'items_reserved' => count($items)]; - } - - public static function release(string $reservationId): void - { - // Release inventory reservation - echo "Released inventory reservation: $reservationId\n"; - } -} - -final class EmailService -{ - private static int $failures = 0; - - public static function send(array $emailData): array - { - self::$failures++; - - if (self::$failures <= 1) { - throw new ExternalServiceException('Email service unavailable'); - } - - return array_merge($emailData, ['email_id' => 'email_'.uniqid(), 'sent' => true]); - } - - public static function reset(): void - { - self::$failures = 0; - } -} - -final class DatabaseService -{ - private static bool $shouldFail = false; - - public static function createOrder(array $data): array - { - if (self::$shouldFail) { - throw new DatabaseException('Database connection failed'); - } - - return array_merge($data, ['order_id' => 'ord_'.uniqid(), 'created' => true]); - } - - public static function deleteOrder(string $orderId): void - { - echo "Deleted order: $orderId\n"; - } - - public static function setShouldFail(bool $shouldFail): void - { - self::$shouldFail = $shouldFail; - } -} - -echo "=== Comprehensive Error Handling Patterns Demo ===\n\n"; - -// Example 1: E-commerce Order Processing with Complex Error Handling -echo "1. E-commerce Order Processing with Complex Error Handling\n"; -echo "==========================================================\n"; - -try { - PaymentService::reset(); - EmailService::reset(); - - $orderData = [ - 'customer_id' => 'cust_123', - 'items' => [ - ['id' => 'item_1', 'quantity' => 2, 'price' => 29.99], - ['id' => 'item_2', 'quantity' => 1, 'price' => 15.99], - ], - 'total' => 75.97, - 'email' => 'customer@example.com', - ]; - - $result = Flowpipe::make() - ->send($orderData) - - // Step 1: Validate order (with fallback to basic validation) - ->withFallback(function ($payload, $error) { - echo "โš ๏ธ Order validation failed, using basic validation: {$error->getMessage()}\n"; - - return array_merge($payload, ['validation_mode' => 'basic']); - }) - ->through([ - function ($data, $next) { - // Simulate validation that might fail - if (rand(1, 10) > 8) { - throw new ValidationException('Advanced validation service unavailable'); - } - - return $next(array_merge($data, ['validated' => true])); - }, - ]) - - // Step 2: Reserve inventory (with compensation) - ->withCompensation(function ($payload, $error, $context) { - echo "๐Ÿ”„ Inventory reservation failed, releasing reservation\n"; - if (isset($payload['reservation_id'])) { - InventoryService::release($payload['reservation_id']); - } - - return array_merge($payload, ['inventory_compensated' => true]); - }) - ->through([ - function ($data, $next) { - $reservation = InventoryService::reserve($data['items']); - - return $next(array_merge($data, $reservation)); - }, - ]) - - // Step 3: Process payment (with retry and compensation) - ->withErrorHandler( - CompositeStrategy::make() - ->retry(RetryStrategy::exponentialBackoff(3, 200, 2.0)) - ->compensate(CompensationStrategy::make(function ($payload, $error, $context) { - echo "๐Ÿ”„ Payment failed, releasing inventory reservation\n"; - if (isset($payload['reservation_id'])) { - InventoryService::release($payload['reservation_id']); - } - - return array_merge($payload, ['payment_compensated' => true]); - })) - ) - ->through([ - function ($data, $next) { - $payment = PaymentService::charge($data); - - return $next(array_merge($data, $payment)); - }, - ]) - - // Step 4: Create order record (with compensation) - ->withCompensation(function ($payload, $error, $context) { - echo "๐Ÿ”„ Order creation failed, refunding payment\n"; - if (isset($payload['payment_id'])) { - PaymentService::refund($payload['payment_id']); - } - if (isset($payload['reservation_id'])) { - InventoryService::release($payload['reservation_id']); - } - - return array_merge($payload, ['order_compensated' => true]); - }) - ->through([ - function ($data, $next) { - $order = DatabaseService::createOrder($data); - - return $next(array_merge($data, $order)); - }, - ]) - - // Step 5: Send confirmation email (with retry and fallback) - ->withErrorHandler( - CompositeStrategy::make() - ->retry(RetryStrategy::linearBackoff(3, 100, 50)) - ->fallback(FallbackStrategy::make(function ($payload, $error) { - echo "โš ๏ธ Email sending failed, queuing for later: {$error->getMessage()}\n"; - - return array_merge($payload, ['email_queued' => true]); - })) - ) - ->through([ - function ($data, $next) { - $email = EmailService::send([ - 'to' => $data['email'], - 'subject' => 'Order Confirmation', - 'order_id' => $data['order_id'], - ]); - - return $next(array_merge($data, $email)); - }, - ]) - - ->thenReturn(); - - echo "โœ… Order processed successfully!\n"; - echo " Order ID: {$result['order_id']}\n"; - echo " Payment ID: {$result['payment_id']}\n"; - echo " Items Reserved: {$result['items_reserved']}\n"; - echo ' Email Status: '.(isset($result['email_id']) ? "Sent ({$result['email_id']})" : 'Queued')."\n"; - -} catch (Exception $e) { - echo "โŒ Order processing failed: {$e->getMessage()}\n"; -} - -echo "\n"; - -// Example 2: User Registration with Multiple Service Dependencies -echo "2. User Registration with Multiple Service Dependencies\n"; -echo "======================================================\n"; - -try { - $userData = [ - 'name' => 'John Doe', - 'email' => 'john@example.com', - 'password' => 'secure_password', - 'plan' => 'premium', - ]; - - $result = Flowpipe::make() - ->send($userData) - - // Step 1: Create user account (with retry) - ->exponentialBackoff(3, 100, 2.0) - ->through([ - function ($data, $next) { - if (rand(1, 10) > 8) { - throw new DatabaseException('Database temporarily unavailable'); - } - - return $next(array_merge($data, ['user_id' => 'user_'.uniqid(), 'created' => true])); - }, - ]) - - // Step 2: Setup user profile (with fallback) - ->withFallback(function ($payload, $error) { - echo "โš ๏ธ Profile setup failed, using basic profile: {$error->getMessage()}\n"; - - return array_merge($payload, ['profile_type' => 'basic']); - }) - ->through([ - function ($data, $next) { - if (rand(1, 10) > 7) { - throw new ExternalServiceException('Profile service unavailable'); - } - - return $next(array_merge($data, ['profile_created' => true, 'profile_type' => 'full'])); - }, - ]) - - // Step 3: Setup billing (with compensation) - ->withCompensation(function ($payload, $error, $context) { - echo "๐Ÿ”„ Billing setup failed, deleting user account\n"; - if (isset($payload['user_id'])) { - DatabaseService::deleteOrder($payload['user_id']); // Simulate user deletion - } - - return array_merge($payload, ['billing_compensated' => true]); - }) - ->through([ - function ($data, $next) { - if ($data['plan'] === 'premium' && rand(1, 10) > 8) { - throw new PaymentException('Billing setup failed'); - } - - return $next(array_merge($data, ['billing_setup' => true])); - }, - ]) - - // Step 4: Send welcome email (with retry and fallback) - ->withErrorHandler( - CompositeStrategy::make() - ->retry(RetryStrategy::make(2, 50)) - ->fallback(FallbackStrategy::make(function ($payload, $error) { - echo "โš ๏ธ Welcome email failed, will send later: {$error->getMessage()}\n"; - - return array_merge($payload, ['welcome_email_queued' => true]); - })) - ) - ->through([ - function ($data, $next) { - if (rand(1, 10) > 6) { - throw new ExternalServiceException('Email service timeout'); - } - - return $next(array_merge($data, ['welcome_email_sent' => true])); - }, - ]) - - ->thenReturn(); - - echo "โœ… User registration successful!\n"; - echo " User ID: {$result['user_id']}\n"; - echo " Profile Type: {$result['profile_type']}\n"; - echo ' Billing Setup: '.($result['billing_setup'] ? 'Yes' : 'No')."\n"; - echo ' Welcome Email: '.(isset($result['welcome_email_sent']) ? 'Sent' : 'Queued')."\n"; - -} catch (Exception $e) { - echo "โŒ User registration failed: {$e->getMessage()}\n"; -} - -echo "\n"; - -// Example 3: File Processing with Advanced Error Handling -echo "3. File Processing with Advanced Error Handling\n"; -echo "==============================================\n"; - -try { - $fileData = [ - 'file_path' => '/uploads/data.csv', - 'format' => 'csv', - 'destination' => '/processed/', - ]; - - $result = Flowpipe::make() - ->send($fileData) - - // Step 1: Validate file (with fallback) - ->withFallback(function ($payload, $error) { - echo "โš ๏ธ File validation failed, using basic validation: {$error->getMessage()}\n"; - - return array_merge($payload, ['validation_mode' => 'basic']); - }) - ->through([ - function ($data, $next) { - if (rand(1, 10) > 9) { - throw new ValidationException('File format validation service unavailable'); - } - - return $next(array_merge($data, ['validated' => true])); - }, - ]) - - // Step 2: Process file (with retry and compensation) - ->withErrorHandler( - CompositeStrategy::make() - ->retry(RetryStrategy::exponentialBackoff(3, 500, 2.0)) - ->compensate(CompensationStrategy::make(function ($payload, $error, $context) { - echo "๐Ÿ”„ File processing failed, cleaning up temporary files\n"; - - return array_merge($payload, ['cleanup_performed' => true]); - })) - ) - ->through([ - function ($data, $next) { - if (rand(1, 10) > 7) { - throw new ExternalServiceException('File processing service overloaded'); - } - - return $next(array_merge($data, ['processed' => true, 'output_file' => '/processed/data_processed.csv'])); - }, - ]) - - // Step 3: Store results (with fallback to queue) - ->withFallback(function ($payload, $error) { - echo "โš ๏ธ Storage failed, queuing for later: {$error->getMessage()}\n"; - - return array_merge($payload, ['queued_for_storage' => true]); - }) - ->through([ - function ($data, $next) { - if (rand(1, 10) > 8) { - throw new DatabaseException('Storage service unavailable'); - } - - return $next(array_merge($data, ['stored' => true, 'storage_id' => 'stor_'.uniqid()])); - }, - ]) - - ->thenReturn(); - - echo "โœ… File processing successful!\n"; - echo " Validation Mode: {$result['validation_mode']}\n"; - echo ' Processed: '.($result['processed'] ? 'Yes' : 'No')."\n"; - echo ' Storage Status: '.(isset($result['stored']) ? "Stored ({$result['storage_id']})" : 'Queued')."\n"; - -} catch (Exception $e) { - echo "โŒ File processing failed: {$e->getMessage()}\n"; -} - -echo "\n"; - -// Example 4: API Integration with Circuit Breaker Pattern -echo "4. API Integration with Circuit Breaker Pattern\n"; -echo "==============================================\n"; - -try { - $apiData = [ - 'endpoint' => 'https://api.example.com/users', - 'method' => 'GET', - 'timeout' => 5, - ]; - - $result = Flowpipe::make() - ->send($apiData) - - // Step 1: Try primary API (with retry) - ->withErrorHandler( - CompositeStrategy::make() - ->retry(RetryStrategy::exponentialBackoff(3, 200, 2.0)) - ->fallback(FallbackStrategy::make(function ($payload, $error) { - echo "โš ๏ธ Primary API failed, trying secondary API: {$error->getMessage()}\n"; - - return array_merge($payload, ['use_secondary' => true]); - })) - ) - ->through([ - function ($data, $next) { - if (rand(1, 10) > 6) { - throw new NetworkException('Primary API timeout'); - } - - return $next(array_merge($data, ['primary_response' => ['data' => 'primary_data'], 'source' => 'primary'])); - }, - ]) - - // Step 2: Try secondary API if primary failed - ->through([ - function ($data, $next) { - if (isset($data['use_secondary']) && $data['use_secondary']) { - if (rand(1, 10) > 8) { - throw new NetworkException('Secondary API also failed'); - } - - return $next(array_merge($data, ['secondary_response' => ['data' => 'secondary_data'], 'source' => 'secondary'])); - } - - return $next($data); - }, - ]) - - // Step 3: Process response (with fallback to cached data) - ->withFallback(function ($payload, $error) { - echo "โš ๏ธ Response processing failed, using cached data: {$error->getMessage()}\n"; - - return array_merge($payload, ['response' => ['data' => 'cached_data'], 'source' => 'cache']); - }) - ->through([ - function ($data, $next) { - if (rand(1, 10) > 9) { - throw new ExternalServiceException('Response processing service failed'); - } - - $response = $data['primary_response'] ?? $data['secondary_response'] ?? null; - if (! $response) { - throw new ExternalServiceException('No response data available'); - } - - return $next(array_merge($data, ['response' => $response])); - }, - ]) - - ->thenReturn(); - - echo "โœ… API integration successful!\n"; - echo " Data Source: {$result['source']}\n"; - echo ' Response: '.json_encode($result['response'])."\n"; - -} catch (Exception $e) { - echo "โŒ API integration failed: {$e->getMessage()}\n"; -} - -echo "\n=== Error Handling Pattern Summary ===\n"; -echo "โœ… Retry Patterns:\n"; -echo " โ€ข Exponential backoff for transient failures\n"; -echo " โ€ข Linear backoff for predictable delays\n"; -echo " โ€ข Custom retry logic based on exception types\n"; -echo " โ€ข Configurable maximum attempts and delays\n\n"; - -echo "โœ… Fallback Patterns:\n"; -echo " โ€ข Default values when services are unavailable\n"; -echo " โ€ข Alternative service endpoints\n"; -echo " โ€ข Cached data as fallback\n"; -echo " โ€ข Graceful degradation of functionality\n\n"; - -echo "โœ… Compensation Patterns:\n"; -echo " โ€ข Automatic rollback of database transactions\n"; -echo " โ€ข Release of reserved resources\n"; -echo " โ€ข Cleanup of temporary files\n"; -echo " โ€ข Saga pattern for distributed transactions\n\n"; - -echo "โœ… Composite Patterns:\n"; -echo " โ€ข Retry + Fallback for maximum resilience\n"; -echo " โ€ข Retry + Compensation for resource management\n"; -echo " โ€ข Multi-layered error handling strategies\n"; -echo " โ€ข Exception-specific handling approaches\n\n"; - -echo "๐Ÿ›ก๏ธ Benefits:\n"; -echo " โ€ข Improved system resilience\n"; -echo " โ€ข Better user experience\n"; -echo " โ€ข Reduced manual intervention\n"; -echo " โ€ข Maintainable error handling code\n"; -echo " โ€ข Comprehensive error logging and monitoring\n"; diff --git a/examples/flows/content-moderation.yaml b/examples/flows/content-moderation.yaml deleted file mode 100644 index e508bfb..0000000 --- a/examples/flows/content-moderation.yaml +++ /dev/null @@ -1,42 +0,0 @@ -flow: content-moderation -description: Automated content moderation with AI analysis and human review workflow - -steps: - - type: action - name: analyze-content-safety - class: Examples\Steps\ContentModeration\AnalyzeContentSafetyStep - description: Analyze content for safety violations using AI - - - type: action - name: check-spam-indicators - class: Examples\Steps\ContentModeration\CheckSpamIndicatorsStep - description: Check for spam indicators and patterns - - - type: action - name: auto-approve-safe-content - class: Examples\Steps\ContentModeration\AutoApproveSafeContentStep - description: Automatically approve content that passes all checks - condition: - class: Examples\Conditions\ContentModeration\ContentIsSafeCondition - - - type: action - name: flag-for-human-review - class: Examples\Steps\ContentModeration\FlagForHumanReviewStep - description: Flag questionable content for human review - condition: - class: Examples\Conditions\ContentModeration\RequiresHumanReviewCondition - - - type: action - name: notify-content-author - class: Examples\Steps\ContentModeration\NotifyContentAuthorStep - description: Notify content author of moderation status - - - type: action - name: update-content-status - class: Examples\Steps\ContentModeration\UpdateContentStatusStep - description: Update content moderation status in database - - - type: action - name: log-moderation-action - class: Examples\Steps\ContentModeration\LogModerationActionStep - description: Log moderation action for audit trail diff --git a/examples/flows/data-processing.yaml b/examples/flows/data-processing.yaml deleted file mode 100644 index d37bba4..0000000 --- a/examples/flows/data-processing.yaml +++ /dev/null @@ -1,50 +0,0 @@ -flow: data-processing -description: ETL pipeline for data transformation, validation, and analysis - -steps: - - type: action - name: extract-raw-data - class: Examples\Steps\DataProcessing\ExtractRawDataStep - description: Extract raw data from various sources - - - type: action - name: validate-data-format - class: Examples\Steps\DataProcessing\ValidateDataFormatStep - description: Validate data format and schema - - - type: action - name: clean-data - class: Examples\Steps\DataProcessing\CleanDataStep - description: Clean and normalize data - - - type: action - name: transform-data - class: Examples\Steps\DataProcessing\TransformDataStep - description: Transform data to target format - - - type: action - name: enrich-data - class: Examples\Steps\DataProcessing\EnrichDataStep - description: Enrich data with additional information - condition: - class: Examples\Conditions\DataProcessing\DataEnrichmentEnabledCondition - - - type: action - name: validate-transformed-data - class: Examples\Steps\DataProcessing\ValidateTransformedDataStep - description: Validate transformed data quality - - - type: action - name: load-to-warehouse - class: Examples\Steps\DataProcessing\LoadToWarehouseStep - description: Load processed data to data warehouse - - - type: action - name: generate-data-report - class: Examples\Steps\DataProcessing\GenerateDataReportStep - description: Generate data processing report - - - type: action - name: notify-stakeholders - class: Examples\Steps\DataProcessing\NotifyStakeholdersStep - description: Notify stakeholders of processing completion diff --git a/examples/flows/ecommerce-order-groups.yaml b/examples/flows/ecommerce-order-groups.yaml deleted file mode 100644 index b256eb0..0000000 --- a/examples/flows/ecommerce-order-groups.yaml +++ /dev/null @@ -1,73 +0,0 @@ -# Example: E-commerce Order Flow with Groups and Nested Flows -# This YAML defines a complete order processing workflow - -flow: EcommerceOrderFlow -description: Process e-commerce orders with validation, payment, and fulfillment - -send: - items: - - name: "Product A" - price: 25.99 - quantity: 2 - - name: "Product B" - price: 15.50 - quantity: 1 - customer: - name: "John Doe" - email: "john@example.com" - address: "123 Main St" - total: 67.48 - -steps: - # Step 1: Use validation group - - type: group - name: order-validation - - # Step 2: Use inventory group - - type: group - name: inventory-management - - # Step 3: Payment processing in nested flow - - type: nested - description: Process payment securely - steps: - - type: closure - action: generate_payment_id - - - type: closure - action: process_payment - - - type: closure - action: calculate_fees - - - condition: - field: payment_status - operator: equals - value: "success" - then: - - type: closure - action: confirm_payment - else: - - type: closure - action: handle_payment_failure - - # Step 4: Order creation in nested flow - - type: nested - description: Create order record - steps: - - type: closure - action: generate_order_id - - - type: closure - action: create_order_record - - - type: closure - action: set_delivery_estimate - - # Step 5: Use notification group - - type: group - name: order-notifications - - # Step 6: Final processing - - type: closure - action: mark_completed \ No newline at end of file diff --git a/examples/flows/invalid-validation-demo.yaml b/examples/flows/invalid-validation-demo.yaml deleted file mode 100644 index e109cdb..0000000 --- a/examples/flows/invalid-validation-demo.yaml +++ /dev/null @@ -1,24 +0,0 @@ -flow: InvalidValidationDemo -description: Example flow showing common validation errors -# Missing required 'steps' field - -send: - name: "Demo User" - email: "demo@example.com" - -# This should have steps array but it's missing -invalid_field: "This field is not recognized" - -# If steps were here, these would be validation errors: -# steps: -# - type: unsupported_type # Invalid step type -# action: some_action -# - type: closure # Missing required action field -# - type: step # Missing required class field -# - type: condition -# condition: -# field: status -# operator: invalid_operator # Invalid operator -# value: "active" -# - type: group -# name: non_existent_group # Group doesn't exist \ No newline at end of file diff --git a/examples/flows/newsletter-campaign.yaml b/examples/flows/newsletter-campaign.yaml deleted file mode 100644 index 3713723..0000000 --- a/examples/flows/newsletter-campaign.yaml +++ /dev/null @@ -1,51 +0,0 @@ -flow: newsletter-campaign -description: Newsletter campaign management with audience segmentation and scheduling - -steps: - - type: action - name: validate-campaign-data - class: Examples\Steps\Newsletter\ValidateCampaignDataStep - description: Validate campaign configuration and content - - - type: action - name: segment-audience - class: Examples\Steps\Newsletter\SegmentAudienceStep - description: Segment audience based on criteria - - - type: action - name: personalize-content - class: Examples\Steps\Newsletter\PersonalizeContentStep - description: Personalize content for each recipient - - - type: action - name: schedule-delivery - class: Examples\Steps\Newsletter\ScheduleDeliveryStep - description: Schedule newsletter delivery - condition: - class: Examples\Conditions\Newsletter\ScheduledDeliveryCondition - - - type: action - name: send-immediately - class: Examples\Steps\Newsletter\SendImmediatelyStep - description: Send newsletter immediately - condition: - class: Examples\Conditions\Newsletter\ImmediateDeliveryCondition - - - type: action - name: track-delivery-metrics - class: Examples\Steps\Newsletter\TrackDeliveryMetricsStep - description: Track delivery metrics and engagement - - - type: action - name: handle-bounces - class: Examples\Steps\Newsletter\HandleBouncesStep - description: Handle bounce notifications and unsubscribes - - - type: action - name: update-subscriber-preferences - class: Examples\Steps\Newsletter\UpdateSubscriberPreferencesStep - description: Update subscriber preferences based on interactions - - - name: generate-campaign-report - class: Examples\Steps\Newsletter\GenerateCampaignReportStep - description: Generate campaign performance report diff --git a/examples/flows/order-processing.yaml b/examples/flows/order-processing.yaml deleted file mode 100644 index ad52545..0000000 --- a/examples/flows/order-processing.yaml +++ /dev/null @@ -1,55 +0,0 @@ -flow: order-processing -description: E-commerce order processing with inventory management, payment processing, and fulfillment - -steps: - - type: action - name: validate-order-data - class: Examples\Steps\OrderProcessing\ValidateOrderDataStep - description: Validate order data (products, quantities, pricing) - - - type: action - name: check-inventory-availability - class: Examples\Steps\OrderProcessing\CheckInventoryAvailabilityStep - description: Verify product availability and stock levels - - - type: action - name: calculate-order-total - class: Examples\Steps\OrderProcessing\CalculateOrderTotalStep - description: Calculate order total including taxes and shipping - - - type: action - name: process-payment - class: Examples\Steps\OrderProcessing\ProcessPaymentStep - description: Process payment through payment gateway - condition: - class: Examples\Conditions\OrderProcessing\PaymentRequiredCondition - - - type: action - name: reserve-inventory - class: Examples\Steps\OrderProcessing\ReserveInventoryStep - description: Reserve inventory for the order - - - type: action - name: create-order-record - class: Examples\Steps\OrderProcessing\CreateOrderRecordStep - description: Create order record in database - - - type: action - name: send-order-confirmation - class: Examples\Steps\OrderProcessing\SendOrderConfirmationStep - description: Send order confirmation email to customer - - - type: action - name: trigger-fulfillment - class: Examples\Steps\OrderProcessing\TriggerFulfillmentStep - description: Trigger warehouse fulfillment process - condition: - class: Examples\Conditions\OrderProcessing\AutoFulfillmentEnabledCondition - - - name: update-customer-metrics - class: Examples\Steps\OrderProcessing\UpdateCustomerMetricsStep - description: Update customer purchase history and metrics - - - name: log-order-event - class: Examples\Steps\OrderProcessing\LogOrderEventStep - description: Log order processing event for analytics diff --git a/examples/flows/test-relative-namespace.yaml b/examples/flows/test-relative-namespace.yaml deleted file mode 100644 index a9d1f9f..0000000 --- a/examples/flows/test-relative-namespace.yaml +++ /dev/null @@ -1,14 +0,0 @@ -flow: test-relative-namespace -description: Test avec des classes en namespace relatif -steps: - # Cas 1: Classe simple (sera prรฉfixรฉe avec App\Flowpipe\Steps) - - type: action - class: SimpleStep - - # Cas 2: Namespace relatif (sera prรฉfixรฉ avec App\Flowpipe\Steps) - - type: action - class: UserRegistration\ValidateInputStep - - # Cas 3: Namespace complet (utilisรฉ tel quel) - - type: action - class: Examples\Steps\UserRegistration\ValidateInputStep diff --git a/examples/flows/user-registration-groups.yaml b/examples/flows/user-registration-groups.yaml deleted file mode 100644 index 4dcd0fb..0000000 --- a/examples/flows/user-registration-groups.yaml +++ /dev/null @@ -1,98 +0,0 @@ -# Example: User Registration Flow with Groups and Nested Flows -# This YAML defines a complete user registration workflow - -flow: UserRegistrationFlow -description: Register new users with validation, setup, and notifications - -send: - name: "John Doe" - email: "john.doe@example.com" - password: "securepassword123" - terms_accepted: true - marketing_consent: true - -steps: - # Step 1: Validate user input - - type: group - name: user-validation - - # Step 2: Password processing in nested flow (for security isolation) - - type: nested - description: Process password securely - steps: - - type: closure - action: hash_password - - - type: closure - action: remove_plain_password - - - type: closure - action: set_password_metadata - - - condition: - field: password_strength - operator: equals - value: "weak" - then: - - type: closure - action: require_password_change - - # Step 3: Set up user account - - type: group - name: user-setup - - # Step 4: Create user profile in nested flow - - type: nested - description: Set up user profile - steps: - - type: closure - action: create_profile - - - type: closure - action: generate_profile_urls - - - type: closure - action: set_privacy_settings - - - condition: - field: marketing_consent - operator: equals - value: true - then: - - type: closure - action: add_to_marketing_list - - # Step 5: Handle permissions and roles - - type: nested - description: Set user permissions - steps: - - type: closure - action: assign_default_role - - - type: closure - action: set_permissions - - - type: closure - action: set_quota_limits - - - condition: - field: terms_accepted - operator: equals - value: true - then: - - type: closure - action: activate_account - else: - - type: closure - action: require_terms_acceptance - - # Step 6: Send notifications - - type: group - name: user-notifications - - # Step 7: Final setup - - type: closure - action: complete_registration - - - type: closure - action: log_registration \ No newline at end of file diff --git a/examples/flows/user-registration-relative.yaml b/examples/flows/user-registration-relative.yaml deleted file mode 100644 index 44ff146..0000000 --- a/examples/flows/user-registration-relative.yaml +++ /dev/null @@ -1,27 +0,0 @@ -flow: user-registration-relative -description: Complete user registration process with relative namespaces - -send: - name: "John Doe" - email: "john.doe@example.com" - password: "SecurePassword123!" - terms_accepted: true - -steps: - # Avec namespace relatif (sera prรฉfixรฉ avec App\Flowpipe\Steps) - - type: action - name: validate-input - class: UserRegistration\ValidateInputStep - description: Validate user input data (email, password, name) - - # Avec namespace complet (utilisรฉ tel quel) - - type: action - name: check-email-uniqueness - class: Examples\Steps\UserRegistration\CheckEmailUniquenessStep - description: Verify email address is not already registered - - # Classe simple (sera prรฉfixรฉe avec App\Flowpipe\Steps) - - type: action - name: simple-step - class: SimpleValidationStep - description: Simple validation step diff --git a/examples/flows/user-registration.yaml b/examples/flows/user-registration.yaml deleted file mode 100644 index 0b63610..0000000 --- a/examples/flows/user-registration.yaml +++ /dev/null @@ -1,50 +0,0 @@ - -flow: user-registration -description: Complete user registration process with validation, email verification, and profile setup - -send: - name: "John Doe" - email: "john.doe@example.com" - password: "SecurePassword123!" - terms_accepted: true - -steps: - - type: action - name: validate-input - class: Examples\Steps\UserRegistration\ValidateInputStep - description: Validate user input data (email, password, name) - - - type: action - name: check-email-uniqueness - class: Examples\Steps\UserRegistration\CheckEmailUniquenessStep - description: Verify email address is not already registered - - - type: action - name: create-user-account - class: Examples\Steps\UserRegistration\CreateUserAccountStep - description: Create user account in database - - - type: action - name: send-verification-email - class: Examples\Steps\UserRegistration\SendVerificationEmailStep - description: Send email verification link to user - - - type: action - name: setup-user-profile - class: Examples\Steps\UserRegistration\SetupUserProfileStep - description: Initialize user profile with default settings - - - type: action - name: assign-default-role - class: Examples\Steps\UserRegistration\AssignDefaultRoleStep - description: Assign default user role and permissions - - - type: action - name: send-welcome-email - class: Examples\Steps\UserRegistration\SendWelcomeEmailStep - description: Send welcome email to new user - - - type: action - name: log-registration-event - class: Examples\Steps\UserRegistration\LogRegistrationEventStep - description: Log user registration for analytics diff --git a/examples/flows/validation-demo.yaml b/examples/flows/validation-demo.yaml deleted file mode 100644 index aa02768..0000000 --- a/examples/flows/validation-demo.yaml +++ /dev/null @@ -1,47 +0,0 @@ -flow: ValidationDemo -description: Example flow showing all validation features -send: - name: "Demo User" - email: "demo@example.com" - age: 25 - status: "active" - -steps: - # Group step example - - type: group - name: user-validation - - # Conditional step example - - type: condition - condition: - field: status - operator: equals - value: "active" - step: - type: closure - action: process_active_user - - # Nested flow example - - type: nested - steps: - - type: closure - action: log_user_activity - - type: closure - action: update_last_seen - - # Step class example - - type: step - class: App\Flowpipe\Steps\SendNotificationStep - - # Complex conditional example - - type: condition - condition: - field: age - operator: greater_than - value: 18 - step: - type: group - name: adult-user-processing - else: - - type: closure - action: handle_minor_user \ No newline at end of file diff --git a/examples/groups-and-nested-flows.md b/examples/groups-and-nested-flows.md deleted file mode 100644 index 4417574..0000000 --- a/examples/groups-and-nested-flows.md +++ /dev/null @@ -1,485 +0,0 @@ -# Step Groups and Nested Flows Examples - -This document provides comprehensive examples of using Step Groups and Nested Flows in Laravel Flowpipe, including enhanced Mermaid export capabilities with rich color coding. - -## Table of Contents - -1. [Basic Step Groups](#basic-step-groups) -2. [Nested Flows](#nested-flows) -3. [Combining Groups and Nested Flows](#combining-groups-and-nested-flows) -4. [Enhanced Mermaid Export with Colors](#enhanced-mermaid-export-with-colors) -5. [YAML Definitions](#yaml-definitions) -6. [Real-world Examples](#real-world-examples) - -## Enhanced Mermaid Export with Colors - -Laravel Flowpipe now supports rich color coding in Mermaid diagrams for different step types: - -- **Groups**: Blue theme (`๐Ÿ“ฆ Group elements`) -- **Nested Flows**: Light green theme (`๐Ÿ”„ Nested elements`) -- **Conditional Steps**: Orange theme (`โ“ Conditional elements`) -- **Transform Steps**: Pink theme (`๐Ÿ”„ Transform elements`) -- **Validation Steps**: Green theme (`โœ… Validation elements`) -- **Cache Steps**: Yellow theme (`๐Ÿ’พ Cache elements`) -- **Batch Steps**: Purple theme (`๐Ÿ“Š Batch elements`) -- **Retry Steps**: Red theme (`๐Ÿ”„ Retry elements`) - -### Exporting Groups with Enhanced Colors - -```bash -# Export a group with enhanced color coding -php artisan flowpipe:export user-validation --type=group --format=mermaid - -# Export a flow with all color-coded steps -php artisan flowpipe:export user-registration --format=mermaid - -# Export to markdown with embedded colored diagram -php artisan flowpipe:export user-registration --format=md --output=docs/user-registration.md -``` - -## Basic Step Groups - -### Defining and Using Groups - -```php -use Grazulex\LaravelFlowpipe\Flowpipe; - -// Define text processing group -Flowpipe::group('text-processing', [ - fn($data, $next) => $next(trim($data)), - fn($data, $next) => $next(strtoupper($data)), - fn($data, $next) => $next(str_replace(' ', '-', $data)), -]); - -// Define validation group -Flowpipe::group('text-validation', [ - fn($data, $next) => $next(strlen($data) > 0 ? $data : throw new InvalidArgumentException('Empty text')), - fn($data, $next) => $next(preg_match('/^[A-Z-]+$/', $data) ? $data : throw new InvalidArgumentException('Invalid format')), -]); - -// Use groups in a flow -$result = Flowpipe::make() - ->send(' hello world ') - ->useGroup('text-processing') - ->useGroup('text-validation') - ->through([ - fn($data, $next) => $next($data . '!'), - ]) - ->thenReturn(); - -// Result: "HELLO-WORLD!" -``` - -### Group Management - -```php -// Check if groups exist -if (Flowpipe::hasGroup('text-processing')) { - echo "Group exists!\n"; -} - -// Get all groups -$groups = Flowpipe::getGroups(); -foreach ($groups as $name => $steps) { - echo "Group: $name has " . count($steps) . " steps\n"; -} - -// Clear all groups (useful for testing) -Flowpipe::clearGroups(); -``` - -## Nested Flows - -### Basic Nested Flow - -```php -$result = Flowpipe::make() - ->send(['name' => 'John Doe', 'email' => 'john@example.com']) - ->nested([ - // This sub-flow runs independently - fn($data, $next) => $next(array_merge($data, ['processed' => true])), - fn($data, $next) => $next(array_merge($data, ['id' => uniqid()])), - ]) - ->through([ - // Main flow continues here - fn($data, $next) => $next(array_merge($data, ['completed' => true])), - ]) - ->thenReturn(); - -// Result: Array with name, email, processed, id, and completed fields -``` - -### Multiple Nested Flows - -```php -$result = Flowpipe::make() - ->send('raw data') - ->nested([ - // First nested flow: cleaning - fn($data, $next) => $next(trim($data)), - fn($data, $next) => $next(strtolower($data)), - ]) - ->nested([ - // Second nested flow: formatting - fn($data, $next) => $next(ucwords($data)), - fn($data, $next) => $next(str_replace(' ', '-', $data)), - ]) - ->through([ - // Main flow: finalization - fn($data, $next) => $next($data . '-final'), - ]) - ->thenReturn(); - -// Result: "Raw-Data-final" -``` - -## Combining Groups and Nested Flows - -### E-commerce Order Processing - -```php -use Grazulex\LaravelFlowpipe\Flowpipe; - -// Define reusable groups -Flowpipe::group('order-validation', [ - fn($order, $next) => $next(isset($order['items']) && count($order['items']) > 0 ? $order : throw new InvalidArgumentException('No items')), - fn($order, $next) => $next(isset($order['customer']) ? $order : throw new InvalidArgumentException('No customer')), - fn($order, $next) => $next($order['total'] > 0 ? $order : throw new InvalidArgumentException('Invalid total')), -]); - -Flowpipe::group('inventory-check', [ - fn($order, $next) => $next(array_merge($order, ['inventory_checked' => true])), - fn($order, $next) => $next(array_merge($order, ['items_available' => true])), -]); - -Flowpipe::group('notifications', [ - fn($order, $next) => $next(array_merge($order, ['customer_notified' => true])), - fn($order, $next) => $next(array_merge($order, ['admin_notified' => true])), -]); - -// Process order with groups and nested flows -$result = Flowpipe::make() - ->send([ - 'items' => [['name' => 'Product A', 'price' => 10]], - 'customer' => ['name' => 'John', 'email' => 'john@example.com'], - 'total' => 10 - ]) - ->useGroup('order-validation') - ->useGroup('inventory-check') - ->nested([ - // Complex payment processing in isolation - fn($order, $next) => $next(array_merge($order, ['payment_id' => 'pay_' . uniqid()])), - fn($order, $next) => $next(array_merge($order, ['payment_status' => 'completed'])), - fn($order, $next) => $next(array_merge($order, ['payment_date' => date('Y-m-d H:i:s')])), - ]) - ->nested([ - // Fulfillment processing - fn($order, $next) => $next(array_merge($order, ['order_id' => 'order_' . uniqid()])), - fn($order, $next) => $next(array_merge($order, ['status' => 'processing'])), - ]) - ->useGroup('notifications') - ->thenReturn(); - -// Result: Complete order with validation, payment, fulfillment, and notifications -``` - -### User Registration Flow - -```php -// Define user processing groups -Flowpipe::group('user-validation', [ - fn($user, $next) => $next(filter_var($user['email'], FILTER_VALIDATE_EMAIL) ? $user : throw new InvalidArgumentException('Invalid email')), - fn($user, $next) => $next(strlen($user['password']) >= 8 ? $user : throw new InvalidArgumentException('Password too short')), - fn($user, $next) => $next(strlen($user['name']) > 0 ? $user : throw new InvalidArgumentException('Name required')), -]); - -Flowpipe::group('user-setup', [ - fn($user, $next) => $next(array_merge($user, ['id' => uniqid()])), - fn($user, $next) => $next(array_merge($user, ['created_at' => date('Y-m-d H:i:s')])), - fn($user, $next) => $next(array_merge($user, ['status' => 'active'])), -]); - -Flowpipe::group('user-notifications', [ - fn($user, $next) => $next(array_merge($user, ['welcome_email_sent' => true])), - fn($user, $next) => $next(array_merge($user, ['admin_notified' => true])), -]); - -// Complete registration flow -$result = Flowpipe::make() - ->send([ - 'name' => 'John Doe', - 'email' => 'john@example.com', - 'password' => 'securepassword123' - ]) - ->useGroup('user-validation') - ->nested([ - // Password hashing in isolation - fn($user, $next) => $next(array_merge($user, ['password' => password_hash($user['password'], PASSWORD_DEFAULT)])), - ]) - ->useGroup('user-setup') - ->nested([ - // Profile setup - fn($user, $next) => $next(array_merge($user, ['profile_created' => true])), - fn($user, $next) => $next(array_merge($user, ['preferences_set' => true])), - ]) - ->useGroup('user-notifications') - ->thenReturn(); - -// Result: Complete user registration with hashed password, profile, and notifications -``` - -## YAML Definitions - -### Group Definitions - -Create group definitions in YAML: - -```yaml -# groups/text-processing.yaml -group: text-processing -description: Process text data -steps: - - type: closure - action: trim - - type: closure - action: uppercase - - type: closure - action: replace - from: ' ' - to: '-' -``` - -```yaml -# groups/user-validation.yaml -group: user-validation -description: Validate user data -steps: - - type: closure - action: validate_email - - type: closure - action: validate_password - - type: closure - action: validate_name -``` - -### Flow with Groups and Nested Steps - -```yaml -# flows/user-registration.yaml -flow: UserRegistrationFlow -description: Complete user registration with groups and nested flows - -send: - name: "John Doe" - email: "john@example.com" - password: "securepassword123" - -steps: - # Use predefined group - - type: group - name: user-validation - - # Nested flow for password processing - - type: nested - steps: - - type: closure - action: hash_password - - type: closure - action: validate_hash - - # Another group - - type: group - name: user-setup - - # Complex nested flow - - type: nested - steps: - - type: closure - action: create_profile - - type: closure - action: set_preferences - - condition: - field: email_verified - operator: equals - value: true - then: - - type: closure - action: activate_account - else: - - type: closure - action: send_verification - - # Final notifications group - - type: group - name: user-notifications -``` - -## Real-world Examples - -### Enhanced Color Visualization Example - -```php -// Create a flow with multiple step types to showcase colors -Flowpipe::group('data-validation', [ - fn($data, $next) => $next(filter_var($data['email'], FILTER_VALIDATE_EMAIL) ? $data : throw new InvalidArgumentException('Invalid email')), - fn($data, $next) => $next(strlen($data['name']) > 0 ? $data : throw new InvalidArgumentException('Name required')), -]); - -Flowpipe::group('data-processing', [ - fn($data, $next) => $next(array_merge($data, ['processed_at' => now()])), - fn($data, $next) => $next(array_merge($data, ['id' => uniqid()])), -]); - -$result = Flowpipe::make() - ->send(['name' => 'John Doe', 'email' => 'john@example.com']) - ->useGroup('data-validation') // Blue group - ->transform(fn($data) => $data) // Pink transform - ->validate(['email' => 'required']) // Green validation - ->cache('user-data', 3600) // Yellow cache - ->nested([ // Light green nested - fn($data, $next) => $next(array_merge($data, ['nested_processed' => true])), - ]) - ->useGroup('data-processing') // Blue group - ->batch(100) // Purple batch - ->retry(3) // Red retry - ->thenReturn(); - -// Export this flow to see the color-coded visualization: -// php artisan flowpipe:export enhanced-color-demo --format=mermaid -``` - -The exported Mermaid diagram will show: -- Blue boxes for groups (`data-validation`, `data-processing`) -- Pink box for transform step -- Green box for validation step -- Yellow box for cache step -- Light green box for nested flow -- Purple box for batch step -- Red box for retry step - -### Data Processing Pipeline - -```php -// ETL Pipeline with groups and nested flows -Flowpipe::group('data-extraction', [ - fn($config, $next) => $next(array_merge($config, ['raw_data' => 'extracted_data'])), - fn($config, $next) => $next(array_merge($config, ['extraction_time' => microtime(true)])), -]); - -Flowpipe::group('data-validation', [ - fn($data, $next) => $next(array_merge($data, ['validation_passed' => true])), - fn($data, $next) => $next(array_merge($data, ['validation_time' => microtime(true)])), -]); - -Flowpipe::group('data-loading', [ - fn($data, $next) => $next(array_merge($data, ['loaded' => true])), - fn($data, $next) => $next(array_merge($data, ['load_time' => microtime(true)])), -]); - -$result = Flowpipe::make() - ->send(['source' => 'database', 'table' => 'users']) - ->useGroup('data-extraction') - ->useGroup('data-validation') - ->nested([ - // Complex transformations - fn($data, $next) => $next(array_merge($data, ['transformed' => true])), - fn($data, $next) => $next(array_merge($data, ['enriched' => true])), - fn($data, $next) => $next(array_merge($data, ['formatted' => true])), - ]) - ->useGroup('data-loading') - ->thenReturn(); -``` - -### Content Management System - -```php -// CMS workflow with groups and nested flows -Flowpipe::group('content-validation', [ - fn($content, $next) => $next(strlen($content['title']) > 0 ? $content : throw new InvalidArgumentException('Title required')), - fn($content, $next) => $next(strlen($content['body']) > 0 ? $content : throw new InvalidArgumentException('Body required')), -]); - -Flowpipe::group('content-processing', [ - fn($content, $next) => $next(array_merge($content, ['slug' => strtolower(str_replace(' ', '-', $content['title']))])), - fn($content, $next) => $next(array_merge($content, ['word_count' => str_word_count($content['body'])])), -]); - -Flowpipe::group('content-publishing', [ - fn($content, $next) => $next(array_merge($content, ['published_at' => date('Y-m-d H:i:s')])), - fn($content, $next) => $next(array_merge($content, ['status' => 'published'])), -]); - -$result = Flowpipe::make() - ->send([ - 'title' => 'My Blog Post', - 'body' => 'This is the content of my blog post.', - 'author' => 'John Doe' - ]) - ->useGroup('content-validation') - ->useGroup('content-processing') - ->nested([ - // SEO optimization - fn($content, $next) => $next(array_merge($content, ['meta_description' => substr($content['body'], 0, 160)])), - fn($content, $next) => $next(array_merge($content, ['seo_optimized' => true])), - ]) - ->nested([ - // Image processing - fn($content, $next) => $next(array_merge($content, ['featured_image' => 'default.jpg'])), - fn($content, $next) => $next(array_merge($content, ['thumbnails_generated' => true])), - ]) - ->useGroup('content-publishing') - ->thenReturn(); -``` - -## Best Practices - -1. **Group Organization**: Keep groups focused on single responsibilities -2. **Naming**: Use descriptive names for groups and nested flows -3. **Testing**: Test groups and nested flows independently -4. **Documentation**: Document complex group interactions -5. **Error Handling**: Implement proper error handling in nested flows -6. **Performance**: Consider the overhead of nested flows for simple operations - -## Testing Groups and Nested Flows - -```php -use Grazulex\LaravelFlowpipe\Tracer\TestTracer; - -public function test_group_execution() -{ - $tracer = new TestTracer(); - - Flowpipe::group('test-group', [ - fn($data, $next) => $next(strtoupper($data)), - fn($data, $next) => $next($data . '!'), - ]); - - $result = Flowpipe::make() - ->send('hello') - ->useGroup('test-group') - ->withTracer($tracer) - ->thenReturn(); - - $this->assertEquals('HELLO!', $result); - $this->assertCount(1, $tracer->count()); // Group counts as one step -} - -public function test_nested_flow_execution() -{ - $tracer = new TestTracer(); - - $result = Flowpipe::make() - ->send('hello') - ->nested([ - fn($data, $next) => $next(strtoupper($data)), - fn($data, $next) => $next($data . '!'), - ]) - ->withTracer($tracer) - ->thenReturn(); - - $this->assertEquals('HELLO!', $result); - $this->assertCount(1, $tracer->count()); // Nested flow counts as one step -} -``` - -This comprehensive guide shows how to effectively use step groups and nested flows in Laravel Flowpipe for building modular, maintainable workflows. \ No newline at end of file diff --git a/examples/groups/adult-user-processing.yaml b/examples/groups/adult-user-processing.yaml deleted file mode 100644 index 4e896ea..0000000 --- a/examples/groups/adult-user-processing.yaml +++ /dev/null @@ -1,9 +0,0 @@ -group: adult-user-processing -description: Processing steps for adult users -steps: - - type: closure - action: verify_age_requirements - - type: closure - action: apply_adult_user_permissions - - type: closure - action: send_adult_user_welcome \ No newline at end of file diff --git a/examples/groups/inventory-management.yaml b/examples/groups/inventory-management.yaml deleted file mode 100644 index 7b85299..0000000 --- a/examples/groups/inventory-management.yaml +++ /dev/null @@ -1,31 +0,0 @@ -# Example: Inventory Management Group -# File: groups/inventory-management.yaml - -group: inventory-management -description: Manage inventory for e-commerce orders - -steps: - # Check item availability - - type: closure - action: check_item_availability - description: Check if all items are available in requested quantities - - # Reserve inventory items - - type: closure - action: reserve_items - description: Reserve items for this order - - # Calculate shipping costs - - type: closure - action: calculate_shipping - description: Calculate shipping costs based on items and location - - # Update inventory levels - - type: closure - action: update_inventory_levels - description: Update inventory levels after reservation - - # Generate inventory report - - type: closure - action: generate_inventory_report - description: Generate inventory status report for this order \ No newline at end of file diff --git a/examples/groups/notifications.yaml b/examples/groups/notifications.yaml deleted file mode 100644 index ed94ceb..0000000 --- a/examples/groups/notifications.yaml +++ /dev/null @@ -1,31 +0,0 @@ -# Example: Enhanced Notification Group -# File: groups/notifications.yaml - -group: notifications -description: Comprehensive notification system for various events - -steps: - # Send welcome email - - type: closure - action: send_welcome_email - description: Send personalized welcome email to new users - - # Send verification email - - type: closure - action: send_verification_email - description: Send email verification link - - # Send SMS notification (if enabled) - - type: closure - action: send_sms_notification - description: Send SMS notification for important events - - # Log notification event - - type: closure - action: log_notification - description: Log notification event for audit trail - - # Notify admin users - - type: closure - action: notify_admin - description: Notify admin users of important events diff --git a/examples/groups/order-notifications.yaml b/examples/groups/order-notifications.yaml deleted file mode 100644 index 202e349..0000000 --- a/examples/groups/order-notifications.yaml +++ /dev/null @@ -1,31 +0,0 @@ -# Example: Order Notifications Group -# File: groups/order-notifications.yaml - -group: order-notifications -description: Send notifications for e-commerce orders - -steps: - # Send order confirmation email - - type: closure - action: send_order_confirmation - description: Send order confirmation email to customer - - # Send SMS notification - - type: closure - action: send_sms_notification - description: Send SMS notification if customer opted in - - # Update customer account - - type: closure - action: update_customer_account - description: Update customer's order history - - # Notify warehouse - - type: closure - action: notify_warehouse - description: Notify warehouse team about new order - - # Log notification events - - type: closure - action: log_notifications - description: Log all notification events for auditing \ No newline at end of file diff --git a/examples/groups/order-processing.yaml b/examples/groups/order-processing.yaml deleted file mode 100644 index f9d9404..0000000 --- a/examples/groups/order-processing.yaml +++ /dev/null @@ -1,41 +0,0 @@ -# Example: Enhanced Order Processing Group -# File: groups/order-processing.yaml - -group: order-processing -description: Comprehensive e-commerce order processing workflow - -steps: - # Validate order data - - type: closure - action: validate_order_data - description: Validate order structure and required fields - - # Check inventory availability - - type: closure - action: check_inventory - description: Verify all items are in stock - - # Reserve inventory items - - type: closure - action: reserve_inventory - description: Reserve items for this order - - # Process payment - - type: closure - action: process_payment - description: Process payment through payment gateway - - # Update inventory levels - - type: closure - action: update_inventory - description: Update inventory quantities after successful payment - - # Create order record - - type: closure - action: create_order_record - description: Create order record in database - - # Generate order confirmation - - type: closure - action: generate_confirmation - description: Generate order confirmation details diff --git a/examples/groups/order-validation.yaml b/examples/groups/order-validation.yaml deleted file mode 100644 index fddb827..0000000 --- a/examples/groups/order-validation.yaml +++ /dev/null @@ -1,31 +0,0 @@ -# Example: Order Validation Group -# File: groups/order-validation.yaml - -group: order-validation -description: Validate e-commerce order data and requirements - -steps: - # Validate order structure - - type: closure - action: validate_order_structure - description: Ensure order contains required fields - - # Validate customer information - - type: closure - action: validate_customer_info - description: Validate customer name, email, and address - - # Validate items and quantities - - type: closure - action: validate_items - description: Validate item names, prices, and quantities - - # Calculate and verify total - - type: closure - action: verify_order_total - description: Calculate total and verify against provided total - - # Check business rules - - type: closure - action: check_business_rules - description: Apply business rules (minimum order, restrictions, etc.) \ No newline at end of file diff --git a/examples/groups/user-notifications.yaml b/examples/groups/user-notifications.yaml deleted file mode 100644 index 8ef5670..0000000 --- a/examples/groups/user-notifications.yaml +++ /dev/null @@ -1,31 +0,0 @@ -# Example: User Notifications Group -# File: groups/user-notifications.yaml - -group: user-notifications -description: Send notifications for new user registration - -steps: - # Send welcome email - - type: closure - action: send_welcome_email - description: Send welcome email with account details - - # Send verification email - - type: closure - action: send_verification_email - description: Send email verification link - - # Send admin notification - - type: closure - action: notify_admin - description: Notify administrators about new user registration - - # Log registration event - - type: closure - action: log_registration - description: Log user registration event for analytics - - # Update user stats - - type: closure - action: update_user_stats - description: Update application user statistics \ No newline at end of file diff --git a/examples/groups/user-setup.yaml b/examples/groups/user-setup.yaml deleted file mode 100644 index ba24b7f..0000000 --- a/examples/groups/user-setup.yaml +++ /dev/null @@ -1,31 +0,0 @@ -# Example: User Setup Group -# File: groups/user-setup.yaml - -group: user-setup -description: Set up new user account and profile - -steps: - # Generate user ID - - type: closure - action: generate_user_id - description: Generate unique user identifier - - # Hash password - - type: closure - action: hash_password - description: Hash user password securely - - # Create user profile - - type: closure - action: create_user_profile - description: Create initial user profile with default settings - - # Set default preferences - - type: closure - action: set_default_preferences - description: Set default user preferences and settings - - # Generate API key - - type: closure - action: generate_api_key - description: Generate API key for user account \ No newline at end of file diff --git a/examples/groups/user-validation.yaml b/examples/groups/user-validation.yaml deleted file mode 100644 index 942950c..0000000 --- a/examples/groups/user-validation.yaml +++ /dev/null @@ -1,31 +0,0 @@ -# Example: Enhanced User Validation Group -# File: groups/user-validation.yaml - -group: user-validation -description: Comprehensive user input validation with multiple checks - -steps: - # Validate email format - - type: closure - action: validate_email - description: Check email format using filter_var - - # Validate password strength - - type: closure - action: validate_password_strength - description: Check password meets security requirements - - # Validate required fields - - type: closure - action: validate_required_fields - description: Ensure all required fields are present - - # Check for existing user - - type: closure - action: check_user_exists - description: Verify email is not already registered - - # Validate terms acceptance - - type: closure - action: validate_terms_acceptance - description: Ensure user has accepted terms and conditions diff --git a/examples/steps/user-registration/AssignDefaultRoleStep.php b/examples/steps/user-registration/AssignDefaultRoleStep.php deleted file mode 100644 index 5b6bd4d..0000000 --- a/examples/steps/user-registration/AssignDefaultRoleStep.php +++ /dev/null @@ -1,56 +0,0 @@ -where('name', 'user') - ->where('is_default', true) - ->first(); - - if (! $defaultRole) { - throw new RuntimeException('Default user role not found'); - } - - // Assign role to user - DB::table('user_roles')->insert([ - 'user_id' => $userId, - 'role_id' => $defaultRole->id, - 'created_at' => now(), - 'updated_at' => now(), - ]); - - $payload['role_assigned'] = true; - $payload['assigned_role'] = $defaultRole->name; - - return $next($payload); - - } catch (Exception $e) { - throw new RuntimeException('Failed to assign default role: '.$e->getMessage()); - } - } -} diff --git a/examples/steps/user-registration/CheckEmailUniquenessStep.php b/examples/steps/user-registration/CheckEmailUniquenessStep.php deleted file mode 100644 index 15d6611..0000000 --- a/examples/steps/user-registration/CheckEmailUniquenessStep.php +++ /dev/null @@ -1,36 +0,0 @@ -where('email', $email) - ->exists(); - - if ($emailExists) { - throw new InvalidArgumentException('This email address is already registered'); - } - - return $next($payload); - } -} diff --git a/examples/steps/user-registration/CreateUserAccountStep.php b/examples/steps/user-registration/CreateUserAccountStep.php deleted file mode 100644 index d72b171..0000000 --- a/examples/steps/user-registration/CreateUserAccountStep.php +++ /dev/null @@ -1,48 +0,0 @@ -insertGetId([ - 'name' => $payload['name'], - 'email' => $payload['email'], - 'password' => Hash::make($payload['password']), - 'email_verified_at' => null, - 'remember_token' => Str::random(10), - 'created_at' => now(), - 'updated_at' => now(), - ]); - - $userData = [ - 'id' => $userId, - 'name' => $payload['name'], - 'email' => $payload['email'], - 'created_at' => now(), - ]; - - return $next($userData); - - } catch (Exception $e) { - throw new RuntimeException('Failed to create user account: '.$e->getMessage()); - } - } -} diff --git a/examples/steps/user-registration/LogRegistrationEventStep.php b/examples/steps/user-registration/LogRegistrationEventStep.php deleted file mode 100644 index 07e33ad..0000000 --- a/examples/steps/user-registration/LogRegistrationEventStep.php +++ /dev/null @@ -1,45 +0,0 @@ - $payload['id'] ?? null, - 'email' => $payload['email'] ?? null, - 'name' => $payload['name'] ?? null, - 'verification_email_sent' => $payload['verification_email_sent'] ?? false, - 'welcome_email_sent' => $payload['welcome_email_sent'] ?? false, - 'profile_created' => $payload['profile_created'] ?? false, - 'role_assigned' => $payload['role_assigned'] ?? false, - 'timestamp' => now()->toISOString(), - ]); - - // You could also send to analytics service, metrics collector, etc. - - $payload['registration_logged'] = true; - - return $next($payload); - - } catch (Exception $e) { - throw new RuntimeException('Failed to log registration event: '.$e->getMessage()); - } - } -} diff --git a/examples/steps/user-registration/SendVerificationEmailStep.php b/examples/steps/user-registration/SendVerificationEmailStep.php deleted file mode 100644 index 18eb18b..0000000 --- a/examples/steps/user-registration/SendVerificationEmailStep.php +++ /dev/null @@ -1,61 +0,0 @@ -addMinutes(60), - ['id' => $userId, 'hash' => sha1($userData['email'])] - ); - - // Send verification email - Mail::send('emails.verify-email', [ - 'user' => $userData, - 'verificationUrl' => $verificationUrl, - ], function ($message) use ($userData) { - $message->to($userData['email'], $userData['name']) - ->subject('Verify Your Email Address'); - }); - - // Add verification info to payload - $userData['verification_token'] = $verificationToken; - $userData['verification_email_sent'] = true; - - return $next($userData); - - } catch (Exception $e) { - throw new RuntimeException('Failed to send verification email: '.$e->getMessage()); - } - } -} diff --git a/examples/steps/user-registration/SendWelcomeEmailStep.php b/examples/steps/user-registration/SendWelcomeEmailStep.php deleted file mode 100644 index 78d243b..0000000 --- a/examples/steps/user-registration/SendWelcomeEmailStep.php +++ /dev/null @@ -1,46 +0,0 @@ - $userData, - 'loginUrl' => route('login'), - 'supportUrl' => route('support'), - ], function ($message) use ($userData) { - $message->to($userData['email'], $userData['name']) - ->subject('Welcome to Our Platform!'); - }); - - $userData['welcome_email_sent'] = true; - - return $next($userData); - - } catch (Exception $e) { - throw new RuntimeException('Failed to send welcome email: '.$e->getMessage()); - } - } -} diff --git a/examples/steps/user-registration/SetupUserProfileStep.php b/examples/steps/user-registration/SetupUserProfileStep.php deleted file mode 100644 index 7bd52a2..0000000 --- a/examples/steps/user-registration/SetupUserProfileStep.php +++ /dev/null @@ -1,60 +0,0 @@ -insertGetId([ - 'user_id' => $userId, - 'avatar' => null, - 'bio' => '', - 'preferences' => json_encode([ - 'notifications' => [ - 'email' => true, - 'sms' => false, - 'push' => true, - ], - 'privacy' => [ - 'profile_visibility' => 'public', - 'show_email' => false, - ], - 'theme' => 'light', - 'language' => 'en', - ]), - 'created_at' => now(), - 'updated_at' => now(), - ]); - - $payload['profile_id'] = $profileId; - $payload['profile_created'] = true; - - return $next($payload); - - } catch (Exception $e) { - throw new RuntimeException('Failed to setup user profile: '.$e->getMessage()); - } - } -} diff --git a/examples/steps/user-registration/ValidateInputStep.php b/examples/steps/user-registration/ValidateInputStep.php deleted file mode 100644 index 7fd7414..0000000 --- a/examples/steps/user-registration/ValidateInputStep.php +++ /dev/null @@ -1,33 +0,0 @@ - 'required|email|max:255', - 'password' => 'required|min:8|regex:/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/', - 'name' => 'required|string|max:255', - 'terms_accepted' => 'required|boolean|accepted', - ]); - - if ($validator->fails()) { - throw new InvalidArgumentException('Validation failed: '.implode(', ', $validator->errors()->all())); - } - - return $next($validator->validated()); - } -} diff --git a/examples/test-enhanced-colors.php b/examples/test-enhanced-colors.php deleted file mode 100755 index 7e59add..0000000 --- a/examples/test-enhanced-colors.php +++ /dev/null @@ -1,255 +0,0 @@ -#!/usr/bin/env php - $next(array_merge($data, ['validated' => true])), -]); - -$result = Flowpipe::make() - ->send(['name' => 'Test User']) - ->useGroup('test-validation') - ->transform(fn ($data) => array_merge($data, ['transformed' => true])) - ->thenReturn(); - -echo " โœ“ Basic flow with group and transform completed\n"; - -// Test 2: Complex color demonstration -echo "\n2. Testing complex color demonstration...\n"; - -Flowpipe::group('complex-validation', [ - fn ($data, $next) => $next(array_merge($data, ['email_valid' => true])), - fn ($data, $next) => $next(array_merge($data, ['name_valid' => true])), -]); - -Flowpipe::group('complex-processing', [ - fn ($data, $next) => $next(array_merge($data, ['processed' => true])), - fn ($data, $next) => $next(array_merge($data, ['id' => uniqid()])), -]); - -$complexResult = Flowpipe::make() - ->send(['name' => 'Complex User', 'email' => 'complex@example.com']) - ->useGroup('complex-validation') - ->transform(fn ($data) => array_merge($data, ['name' => mb_strtoupper($data['name'])])) - ->nested([ - fn ($data, $next) => $next(array_merge($data, ['nested_processed' => true])), - fn ($data, $next) => $next(array_merge($data, ['nested_id' => uniqid()])), - ]) - ->useGroup('complex-processing') - ->through([ - fn ($data, $next) => $next(array_merge($data, ['final_step' => true])), - ]) - ->thenReturn(); - -echo " โœ“ Complex flow with multiple color types completed\n"; - -// Test 3: Export command functionality -echo "\n3. Testing export command functionality...\n"; - -// Create a sample definition for testing -$sampleDefinition = [ - 'flow' => 'test-flow', - 'description' => 'Test flow for color coding', - 'steps' => [ - [ - 'type' => 'group', - 'name' => 'validation-group', - ], - [ - 'type' => 'transform', - 'action' => 'uppercase', - ], - [ - 'type' => 'validation', - 'action' => 'validate', - ], - [ - 'type' => 'cache', - 'action' => 'cache', - ], - [ - 'type' => 'nested', - 'action' => 'process', - ], - [ - 'type' => 'batch', - 'action' => 'batch', - ], - [ - 'type' => 'retry', - 'action' => 'retry', - ], - ], -]; - -// Create a simple export command instance for testing -$exportCommand = new class -{ - public function testExport(array $definition, string $flowName): string - { - return $this->exportToMermaid($definition, $flowName); - } - - private function exportToMermaid(array $definition, string $flowName, string $type = 'flow'): string - { - $mermaid = "flowchart TD\n"; - $title = $type === 'group' ? "Group: {$flowName}" : "Flow: {$flowName}"; - - // Enhanced style definitions with improved group colors - $mermaid .= " classDef groupStyle fill:#e1f5fe,stroke:#01579b,stroke-width:2px,color:#01579b\n"; - $mermaid .= " classDef stepStyle fill:#f3e5f5,stroke:#4a148c,stroke-width:2px,color:#4a148c\n"; - $mermaid .= " classDef conditionalStyle fill:#fff3e0,stroke:#e65100,stroke-width:2px,color:#e65100\n"; - $mermaid .= " classDef startEndStyle fill:#e8f5e8,stroke:#2e7d32,stroke-width:3px,color:#2e7d32\n"; - $mermaid .= " classDef nestedStyle fill:#f9fbe7,stroke:#33691e,stroke-width:2px,color:#33691e\n"; - $mermaid .= " classDef transformStyle fill:#fce4ec,stroke:#ad1457,stroke-width:2px,color:#ad1457\n"; - $mermaid .= " classDef validationStyle fill:#e8f5e8,stroke:#2e7d32,stroke-width:2px,color:#2e7d32\n"; - $mermaid .= " classDef cacheStyle fill:#fff8e1,stroke:#ff8f00,stroke-width:2px,color:#ff8f00\n"; - $mermaid .= " classDef batchStyle fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px,color:#7b1fa2\n"; - $mermaid .= " classDef retryStyle fill:#ffebee,stroke:#c62828,stroke-width:2px,color:#c62828\n"; - $mermaid .= "\n"; - - $mermaid .= " Start([๐Ÿš€ Start: {$title}])\n"; - $mermaid .= " Start:::startEndStyle\n"; - - $previousNode = 'Start'; - - if (isset($definition['steps'])) { - $stepCounter = 1; - - foreach ($definition['steps'] as $step) { - $currentNode = "Step{$stepCounter}"; - $stepCounter++; - - if (isset($step['type'])) { - $stepType = $step['type']; - $action = $step['action'] ?? 'process'; - $icon = $this->getStepIcon($stepType); - $styleClass = $this->getStepStyleClass($stepType); - - if ($stepType === 'group') { - $groupName = $step['name'] ?? 'unknown'; - $mermaid .= " {$currentNode}[\"๐Ÿ“ฆ Group: {$groupName}\"]\n"; - $mermaid .= " {$currentNode}:::groupStyle\n"; - } elseif ($stepType === 'nested') { - $mermaid .= " {$currentNode}[\"{$icon} {$stepType}:\\n{$action}\"]\n"; - $mermaid .= " {$currentNode}:::nestedStyle\n"; - } else { - $mermaid .= " {$currentNode}[\"{$icon} {$stepType}:\\n{$action}\"]\n"; - $mermaid .= " {$currentNode}:::{$styleClass}\n"; - } - - $mermaid .= " {$previousNode} --> {$currentNode}\n"; - $previousNode = $currentNode; - } - } - } - - $mermaid .= " End([๐Ÿ End])\n"; - $mermaid .= " End:::startEndStyle\n"; - - return $mermaid." {$previousNode} --> End\n"; - } - - private function getStepStyleClass(string $stepType): string - { - return match ($stepType) { - 'group' => 'groupStyle', - 'nested' => 'nestedStyle', - 'conditional' => 'conditionalStyle', - 'transform' => 'transformStyle', - 'validation' => 'validationStyle', - 'cache' => 'cacheStyle', - 'batch' => 'batchStyle', - 'retry' => 'retryStyle', - default => 'stepStyle' - }; - } - - private function getStepIcon(string $stepType): string - { - return match ($stepType) { - 'closure' => 'โšก', - 'conditional' => 'โ“', - 'class' => '๐Ÿ”ง', - 'step' => '๐Ÿ”ง', - 'group' => '๐Ÿ“ฆ', - 'nested' => '๐Ÿ”„', - 'transform' => '๐Ÿ”„', - 'validation' => 'โœ…', - 'cache' => '๐Ÿ’พ', - 'batch' => '๐Ÿ“Š', - 'retry' => '๐Ÿ”„', - default => 'โš™๏ธ' - }; - } -}; - -$mermaidOutput = $exportCommand->testExport($sampleDefinition, 'test-flow'); -echo " โœ“ Export command functionality tested\n"; - -// Test 4: Show the generated Mermaid output -echo "\n4. Generated Mermaid output sample:\n"; -echo "---\n"; -echo $mermaidOutput; -echo "---\n"; - -// Test 5: Verify color styles -echo "\n5. Verifying color styles...\n"; -$expectedStyles = [ - 'groupStyle' => 'fill:#e1f5fe,stroke:#01579b', - 'transformStyle' => 'fill:#fce4ec,stroke:#ad1457', - 'validationStyle' => 'fill:#e8f5e8,stroke:#2e7d32', - 'cacheStyle' => 'fill:#fff8e1,stroke:#ff8f00', - 'nestedStyle' => 'fill:#f9fbe7,stroke:#33691e', - 'batchStyle' => 'fill:#f3e5f5,stroke:#7b1fa2', - 'retryStyle' => 'fill:#ffebee,stroke:#c62828', -]; - -foreach ($expectedStyles as $style => $colors) { - if (mb_strpos($mermaidOutput, $style) !== false && mb_strpos($mermaidOutput, $colors) !== false) { - echo " โœ“ {$style} color definition found\n"; - } else { - echo " โœ— {$style} color definition missing\n"; - } -} - -echo "\n=== Test Summary ===\n"; -echo "โœ“ Basic flow functionality works\n"; -echo "โœ“ Complex flow with multiple step types works\n"; -echo "โœ“ Export command generates proper Mermaid output\n"; -echo "โœ“ Color styles are properly defined\n"; -echo "โœ“ All step types have appropriate icons and colors\n"; - -echo "\n=== Manual Testing Commands ===\n"; -echo "Run these commands in a Laravel project with Flowpipe installed:\n"; -echo "php artisan flowpipe:export test-flow --format=mermaid\n"; -echo "php artisan flowpipe:export validation-group --type=group --format=mermaid\n"; -echo "php artisan flowpipe:export test-flow --format=md --output=docs/test-flow.md\n"; - -echo "\n=== Color Legend ===\n"; -echo "๐Ÿ“ฆ Blue: Groups\n"; -echo "๐Ÿ”„ Pink: Transform steps\n"; -echo "โœ… Green: Validation steps\n"; -echo "๐Ÿ’พ Yellow: Cache steps\n"; -echo "๐Ÿ”„ Light Green: Nested flows\n"; -echo "๐Ÿ“Š Purple: Batch steps\n"; -echo "๐Ÿ”„ Red: Retry steps\n"; - -echo "\nTest completed successfully!\n"; diff --git a/examples/user-registration-example.md b/examples/user-registration-example.md deleted file mode 100644 index 7793e8d..0000000 --- a/examples/user-registration-example.md +++ /dev/null @@ -1,499 +0,0 @@ -# User Registration Flow Example - -This example demonstrates a complete user registration flow with validation, email verification, and profile setup. - -## Overview - -The user registration flow includes: -1. Input validation -2. Email uniqueness check -3. User account creation -4. Email verification (conditional) -5. Profile setup -6. Role assignment -7. Welcome email (conditional) -8. Event logging - -## Flow Definition - -```yaml -name: user-registration -description: Complete user registration process with validation, email verification, and profile setup - -steps: - - name: validate-input - class: Examples\Steps\UserRegistration\ValidateInputStep - description: Validate user input data (email, password, name) - - - name: check-email-uniqueness - class: Examples\Steps\UserRegistration\CheckEmailUniquenessStep - description: Verify email address is not already registered - - - name: create-user-account - class: Examples\Steps\UserRegistration\CreateUserAccountStep - description: Create user account in database - - - name: send-verification-email - class: Examples\Steps\UserRegistration\SendVerificationEmailStep - description: Send email verification link to user - condition: - class: Examples\Conditions\UserRegistration\EmailVerificationEnabledCondition - - - name: setup-user-profile - class: Examples\Steps\UserRegistration\SetupUserProfileStep - description: Initialize user profile with default settings - - - name: assign-default-role - class: Examples\Steps\UserRegistration\AssignDefaultRoleStep - description: Assign default user role and permissions - - - name: send-welcome-email - class: Examples\Steps\UserRegistration\SendWelcomeEmailStep - description: Send welcome email to new user - condition: - class: Examples\Conditions\UserRegistration\WelcomeEmailEnabledCondition - - - name: log-registration-event - class: Examples\Steps\UserRegistration\LogRegistrationEventStep - description: Log user registration for analytics -``` - -## Usage - -### Basic Usage - -```php - 'John Doe', - 'email' => 'john@example.com', - 'password' => 'SecurePassword123!', - 'terms_accepted' => true, - 'marketing_opt_in' => true, -]; - -// Create flow context -$context = new FlowContext(['user_data' => $userData]); - -// Execute the flow -$result = Flowpipe::run('user-registration', $context); - -// Check if registration was successful -if ($result->isSuccess()) { - $userId = $result->get('user_id'); - echo "User registered successfully with ID: {$userId}"; -} else { - $errors = $result->getErrors(); - echo "Registration failed: " . implode(', ', $errors); -} -``` - -### Advanced Usage with Tracing - -```php - $userData]); - -// Execute with tracing -$result = Flowpipe::run('user-registration', $context, $tracer); - -// Analyze execution -$trace = $tracer->getTrace(); -foreach ($trace as $step) { - echo "Step: {$step['name']}, Duration: {$step['duration']}ms\n"; -} -``` - -### Error Handling - -```php - $userData]); - $result = Flowpipe::run('user-registration', $context); - - if ($result->isSuccess()) { - // Handle successful registration - $userId = $result->get('user_id'); - $verificationSent = $result->get('verification_email_sent', false); - - if ($verificationSent) { - return redirect()->route('verify-email-notice'); - } else { - return redirect()->route('dashboard'); - } - } else { - // Handle validation errors - $errors = $result->getErrors(); - return back()->withErrors($errors)->withInput(); - } - -} catch (\Exception $e) { - // Handle system errors - logger()->error('User registration failed', [ - 'error' => $e->getMessage(), - 'user_data' => $userData, - ]); - - return back()->withErrors(['system' => 'Registration failed. Please try again.']); -} -``` - -## Configuration - -### Application Configuration - -```php -// config/flowpipe.php -return [ - 'definitions_path' => 'flow_definitions', - 'step_namespace' => 'App\\Flowpipe\\Steps', - 'tracing' => [ - 'enabled' => true, - 'default' => \Grazulex\LaravelFlowpipe\Tracer\BasicTracer::class, - ], -]; -``` - -### Custom Application Settings - -You can add your own configuration for the registration flow: - -```php -// config/app.php -return [ - // ... other config - 'user_registration' => [ - 'email_verification_enabled' => true, - 'welcome_email_enabled' => true, - 'default_user_role' => 'user', - ], -]; -``` - -## Database Tables - -This example assumes the following database structure: - -### Users Table - -```sql -CREATE TABLE users ( - id BIGINT PRIMARY KEY AUTO_INCREMENT, - name VARCHAR(255) NOT NULL, - email VARCHAR(255) UNIQUE NOT NULL, - password VARCHAR(255) NOT NULL, - email_verified_at TIMESTAMP NULL, - remember_token VARCHAR(100), - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -); -``` - -### User Profiles Table - -```sql -CREATE TABLE user_profiles ( - id BIGINT PRIMARY KEY AUTO_INCREMENT, - user_id BIGINT NOT NULL, - avatar VARCHAR(255), - bio TEXT, - preferences JSON, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE -); -``` - -### Roles Table - -```sql -CREATE TABLE roles ( - id BIGINT PRIMARY KEY AUTO_INCREMENT, - name VARCHAR(255) UNIQUE NOT NULL, - is_default BOOLEAN DEFAULT FALSE, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -); -``` - -### User Roles Table - -```sql -CREATE TABLE user_roles ( - id BIGINT PRIMARY KEY AUTO_INCREMENT, - user_id BIGINT NOT NULL, - role_id BIGINT NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, - FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE -); -``` - -## Email Templates - -### Verification Email Template - -```php - - - - - Verify Your Email - - -

Welcome {{ $user['name'] }}!

-

Please verify your email address by clicking the button below:

- - Verify Email - -

If you didn't create an account, you can safely ignore this email.

- - -``` - -### Welcome Email Template - -```php - - - - - Welcome to Our Platform - - -

Welcome {{ $user['name'] }}!

-

Your account has been created successfully. You can now:

- -

- Login to your account -

-

- If you need help, visit our support center. -

- - -``` - -## Testing - -### Unit Tests - -```php - [ - 'name' => 'John Doe', - 'email' => 'john@example.com', - 'password' => 'SecurePassword123!', - 'terms_accepted' => true, - ] - ]); - - $result = $step->handle($context); - - $this->assertTrue($result->get('validation_passed')); - $this->assertFalse($result->hasErrors()); - } - - public function test_fails_with_invalid_email() - { - $step = new ValidateInputStep(); - $context = new FlowContext([ - 'user_data' => [ - 'name' => 'John Doe', - 'email' => 'invalid-email', - 'password' => 'SecurePassword123!', - 'terms_accepted' => true, - ] - ]); - - $result = $step->handle($context); - - $this->assertFalse($result->get('validation_passed')); - $this->assertTrue($result->hasErrors()); - } -} -``` - -### Integration Tests - -```php - [ - 'name' => 'John Doe', - 'email' => 'john@example.com', - 'password' => 'password123', - ] - ]); - - $result = $step->handle($context); - - $this->assertTrue($result->get('user_created')); - $this->assertIsInt($result->get('user_id')); - $this->assertDatabaseHas('users', [ - 'email' => 'john@example.com', - 'name' => 'John Doe', - ]); - } -} -``` - -## Customization - -### Custom Validation Rules - -```php -get('user_data', []); - - $validator = Validator::make($userData, [ - 'email' => [ - 'required', - 'email', - 'max:255', - // Custom rule to check against disposable email domains - function ($attribute, $value, $fail) { - if ($this->isDisposableEmail($value)) { - $fail('Disposable email addresses are not allowed.'); - } - }, - ], - 'password' => 'required|min:8|confirmed', - 'name' => 'required|string|max:255', - ]); - - // Rest of the validation logic... - } - - private function isDisposableEmail(string $email): bool - { - $domain = substr(strrchr($email, "@"), 1); - $disposableDomains = ['10minutemail.com', 'tempmail.org']; - - return in_array($domain, $disposableDomains); - } -} -``` - -### Custom Profile Setup - -```php -get('user_id'); - $userData = $context->get('validated_data', []); - - // Custom profile setup based on user type - $userType = $userData['account_type'] ?? 'personal'; - - $preferences = $this->getDefaultPreferences($userType); - - // Create profile with custom preferences - $profileId = DB::table('user_profiles')->insertGetId([ - 'user_id' => $userId, - 'account_type' => $userType, - 'preferences' => json_encode($preferences), - 'created_at' => now(), - 'updated_at' => now(), - ]); - - $context->set('profile_id', $profileId); - $context->set('profile_created', true); - - return $context; - } - - private function getDefaultPreferences(string $userType): array - { - $basePreferences = [ - 'notifications' => ['email' => true, 'sms' => false], - 'privacy' => ['profile_visibility' => 'public'], - ]; - - switch ($userType) { - case 'business': - $basePreferences['features'] = ['analytics' => true, 'api_access' => true]; - break; - case 'personal': - $basePreferences['features'] = ['analytics' => false, 'api_access' => false]; - break; - } - - return $basePreferences; - } -} -``` - -This example provides a complete, production-ready user registration flow that can be easily customized and extended for specific application needs. diff --git a/examples/user-registration-groups.php b/examples/user-registration-groups.php deleted file mode 100644 index b13815f..0000000 --- a/examples/user-registration-groups.php +++ /dev/null @@ -1,261 +0,0 @@ - $next( - filter_var($user['email'], FILTER_VALIDATE_EMAIL) - ? $user - : throw new InvalidArgumentException('Invalid email format') - ), - - // Validate password strength - fn ($user, $next) => $next( - mb_strlen($user['password']) >= 8 - ? $user - : throw new InvalidArgumentException('Password must be at least 8 characters') - ), - - // Validate required fields - fn ($user, $next) => $next( - ! empty($user['name']) && ! empty($user['email']) - ? $user - : throw new InvalidArgumentException('Name and email are required') - ), - - // Check for existing user (simplified) - fn ($user, $next) => $next(array_merge($user, [ - 'email_available' => true, // Simplified for example - 'validation_passed' => true, - ])), -]); - -Flowpipe::group('user-setup', [ - // Generate user ID - fn ($user, $next) => $next(array_merge($user, [ - 'user_id' => 'user_'.uniqid(), - 'username' => mb_strtolower(str_replace(' ', '.', $user['name'])), - ])), - - // Set default preferences - fn ($user, $next) => $next(array_merge($user, [ - 'preferences' => [ - 'theme' => 'light', - 'notifications' => true, - 'newsletter' => true, - ], - ])), - - // Set account metadata - fn ($user, $next) => $next(array_merge($user, [ - 'created_at' => date('Y-m-d H:i:s'), - 'status' => 'active', - 'email_verified' => false, - ])), -]); - -Flowpipe::group('user-notifications', [ - // Send welcome email - fn ($user, $next) => $next(array_merge($user, [ - 'welcome_email_sent' => true, - 'welcome_email_id' => 'email_'.uniqid(), - ])), - - // Send verification email - fn ($user, $next) => $next(array_merge($user, [ - 'verification_email_sent' => true, - 'verification_token' => 'token_'.bin2hex(random_bytes(32)), - ])), - - // Notify admin - fn ($user, $next) => $next(array_merge($user, [ - 'admin_notified' => true, - 'admin_notification_id' => 'admin_'.uniqid(), - ])), -]); - -// Sample user registration data -$userData = [ - 'name' => 'John Doe', - 'email' => 'john.doe@example.com', - 'password' => 'securepassword123', - 'terms_accepted' => true, - 'marketing_consent' => true, -]; - -echo "=== User Registration with Groups and Nested Flows ===\n\n"; -echo "Registration Data:\n"; -print_r($userData); -echo "\n"; - -// Process user registration -try { - $result = Flowpipe::make() - ->send($userData) - - // Step 1: Validate user input - ->useGroup('user-validation') - - // Step 2: Process password securely in nested flow - ->nested([ - // Password processing sub-flow (isolated for security) - fn ($user, $next) => $next(array_merge($user, [ - 'password_hash' => password_hash($user['password'], PASSWORD_DEFAULT), - 'password_strength' => 'strong', // Simplified scoring - ])), - - // Remove plain password from array - function ($user, $next) { - unset($user['password']); - - return $next($user); - }, - - // Add security metadata - fn ($user, $next) => $next(array_merge($user, [ - 'password_created_at' => date('Y-m-d H:i:s'), - 'password_expires_at' => date('Y-m-d H:i:s', strtotime('+90 days')), - ])), - ]) - - // Step 3: Set up user account - ->useGroup('user-setup') - - // Step 4: Create user profile in nested flow - ->nested([ - // Profile creation sub-flow - fn ($user, $next) => $next(array_merge($user, [ - 'profile' => [ - 'bio' => '', - 'avatar' => 'default-avatar.png', - 'timezone' => 'UTC', - 'language' => 'en', - ], - ])), - - // Generate profile URLs - fn ($user, $next) => $next(array_merge($user, [ - 'profile_url' => '/profile/'.$user['username'], - 'avatar_url' => '/avatars/default-avatar.png', - ])), - - // Set privacy settings - fn ($user, $next) => $next(array_merge($user, [ - 'privacy' => [ - 'profile_public' => false, - 'email_public' => false, - 'show_online_status' => true, - ], - ])), - ]) - - // Step 5: Handle permissions and roles in nested flow - ->nested([ - // Role assignment sub-flow - fn ($user, $next) => $next(array_merge($user, [ - 'role' => 'user', - 'permissions' => ['read', 'write_own', 'comment'], - ])), - - // Set quota limits - fn ($user, $next) => $next(array_merge($user, [ - 'quota' => [ - 'storage' => '1GB', - 'uploads_per_day' => 10, - 'api_calls_per_hour' => 100, - ], - ])), - ]) - - // Step 6: Send notifications - ->useGroup('user-notifications') - - // Step 7: Final setup and logging - ->through([ - // Final setup step - fn ($user, $next) => $next(array_merge($user, [ - 'registration_completed' => true, - 'completed_at' => date('Y-m-d H:i:s'), - 'onboarding_status' => 'pending', - ])), - - // Log registration - fn ($user, $next) => $next(array_merge($user, [ - 'registration_logged' => true, - 'log_entry_id' => 'log_'.uniqid(), - ])), - ]) - - ->thenReturn(); - - echo "User registration completed successfully!\n\n"; - - echo "=== Registration Summary ===\n"; - echo 'User ID: '.$result['user_id']."\n"; - echo 'Username: '.$result['username']."\n"; - echo 'Email: '.$result['email']."\n"; - echo 'Status: '.$result['status']."\n"; - echo 'Created At: '.$result['created_at']."\n"; - echo 'Profile URL: '.$result['profile_url']."\n"; - echo 'Role: '.$result['role']."\n"; - echo 'Email Verified: '.($result['email_verified'] ? 'Yes' : 'No')."\n"; - echo 'Welcome Email Sent: '.($result['welcome_email_sent'] ? 'Yes' : 'No')."\n"; - echo 'Admin Notified: '.($result['admin_notified'] ? 'Yes' : 'No')."\n"; - - echo "\n=== User Preferences ===\n"; - foreach ($result['preferences'] as $key => $value) { - echo '- '.ucfirst($key).': '.($value ? 'Enabled' : 'Disabled')."\n"; - } - - echo "\n=== User Permissions ===\n"; - foreach ($result['permissions'] as $permission) { - echo '- '.ucfirst(str_replace('_', ' ', $permission))."\n"; - } - - echo "\n=== Privacy Settings ===\n"; - foreach ($result['privacy'] as $setting => $value) { - echo '- '.ucfirst(str_replace('_', ' ', $setting)).': '.($value ? 'Yes' : 'No')."\n"; - } - - echo "\n=== Quota Limits ===\n"; - foreach ($result['quota'] as $limit => $value) { - echo '- '.ucfirst(str_replace('_', ' ', $limit)).': '.$value."\n"; - } - -} catch (Exception $e) { - echo 'Registration failed: '.$e->getMessage()."\n"; -} - -echo "\n=== Workflow Analysis ===\n"; -echo "Groups Used:\n"; -foreach (Flowpipe::getGroups() as $groupName => $steps) { - echo "- $groupName (".count($steps)." steps)\n"; -} - -echo "\nNested Flows Benefits:\n"; -echo "โœ“ Password processing isolated for security\n"; -echo "โœ“ Profile creation encapsulated\n"; -echo "โœ“ Role assignment contained\n"; -echo "โœ“ Each nested flow can be tested independently\n"; -echo "โœ“ Complex logic is organized and maintainable\n"; - -echo "\nGroup Benefits:\n"; -echo "โœ“ Validation logic is reusable\n"; -echo "โœ“ User setup can be reused for different registration flows\n"; -echo "โœ“ Notification logic is centralized\n"; -echo "โœ“ Easy to modify individual components\n"; -echo "โœ“ Clear separation of concerns\n"; diff --git a/examples/user-registration-usage.php b/examples/user-registration-usage.php deleted file mode 100644 index fbdbfd2..0000000 --- a/examples/user-registration-usage.php +++ /dev/null @@ -1,118 +0,0 @@ - 'John Doe', - 'email' => 'john.doe@example.com', - 'password' => 'SecurePassword123!', - 'terms_accepted' => true, -]; - -try { - $result = Flowpipe::make() - ->send($userData) - ->through([ - new ValidateInputStep(), - new CheckEmailUniquenessStep(), - new CreateUserAccountStep(), - new SendVerificationEmailStep(), - new SetupUserProfileStep(), - new AssignDefaultRoleStep(), - new SendWelcomeEmailStep(), - new LogRegistrationEventStep(), - ]) - ->thenReturn(); - - echo "User registration successful!\n"; - echo 'User ID: '.$result['id']."\n"; - echo 'Email: '.$result['email']."\n"; - echo 'Profile created: '.($result['profile_created'] ? 'Yes' : 'No')."\n"; - echo 'Role assigned: '.($result['role_assigned'] ? $result['assigned_role'] : 'No')."\n"; - echo 'Welcome email sent: '.($result['welcome_email_sent'] ? 'Yes' : 'No')."\n"; - -} catch (Exception $e) { - echo 'Registration failed: '.$e->getMessage()."\n"; -} - -// Example 2: User registration with conditional steps -use Grazulex\LaravelFlowpipe\Contracts\Condition; -use Grazulex\LaravelFlowpipe\Steps\ConditionalStep; - -final class EmailVerificationEnabledCondition implements Condition -{ - public function evaluate(mixed $payload): bool - { - return config('auth.email_verification', true); - } -} - -final class WelcomeEmailEnabledCondition implements Condition -{ - public function evaluate(mixed $payload): bool - { - return config('mail.welcome_email', true); - } -} - -$result = Flowpipe::make() - ->send($userData) - ->through([ - new ValidateInputStep(), - new CheckEmailUniquenessStep(), - new CreateUserAccountStep(), - ConditionalStep::when( - new EmailVerificationEnabledCondition(), - new SendVerificationEmailStep() - ), - new SetupUserProfileStep(), - new AssignDefaultRoleStep(), - ConditionalStep::when( - new WelcomeEmailEnabledCondition(), - new SendWelcomeEmailStep() - ), - new LogRegistrationEventStep(), - ]) - ->thenReturn(); - -// Example 3: User registration with error handling and tracing -use Grazulex\LaravelFlowpipe\Tracer\TestTracer; - -$tracer = new TestTracer(); - -try { - $result = Flowpipe::make() - ->send($userData) - ->through([ - new ValidateInputStep(), - new CheckEmailUniquenessStep(), - new CreateUserAccountStep(), - new SendVerificationEmailStep(), - new SetupUserProfileStep(), - new AssignDefaultRoleStep(), - new SendWelcomeEmailStep(), - new LogRegistrationEventStep(), - ]) - ->withTracer($tracer) - ->thenReturn(); - - echo "Registration completed successfully!\n"; - echo 'Steps executed: '.$tracer->count()."\n"; - echo 'First step: '.$tracer->firstStep()."\n"; - echo 'Last step: '.$tracer->lastStep()."\n"; - -} catch (Exception $e) { - echo 'Registration failed at step: '.$tracer->lastStep()."\n"; - echo 'Error: '.$e->getMessage()."\n"; -} diff --git a/examples/validation-command-example.md b/examples/validation-command-example.md deleted file mode 100644 index ca03fa1..0000000 --- a/examples/validation-command-example.md +++ /dev/null @@ -1,209 +0,0 @@ -# Validation Command Example - -This example demonstrates how to use the `flowpipe:validate` command to validate your flow definitions. - -## Example Flow Files - -### Valid Flow (`examples/flows/validation-demo.yaml`) -```yaml -flow: ValidationDemo -description: Example flow showing all validation features -send: - name: "Demo User" - email: "demo@example.com" - age: 25 - status: "active" - -steps: - # Group step example - - type: group - name: user-validation - - # Conditional step example - - type: condition - condition: - field: status - operator: equals - value: "active" - step: - type: closure - action: process_active_user - - # Nested flow example - - type: nested - steps: - - type: closure - action: log_user_activity - - type: closure - action: update_last_seen - - # Action step example (legacy alias for step) - - type: action - class: App\Flowpipe\Steps\SendNotificationStep - - # Complex conditional example - - type: condition - condition: - field: age - operator: greater_than - value: 18 - step: - type: group - name: adult-user-processing - else: - - type: closure - action: handle_minor_user -``` - -### Invalid Flow (`examples/flows/invalid-validation-demo.yaml`) -```yaml -flow: InvalidValidationDemo -description: Example flow showing common validation errors -# Missing required 'steps' field - -send: - name: "Demo User" - email: "demo@example.com" - -# This should have steps array but it's missing -invalid_field: "This field is not recognized" -``` - -## Running Validation - -### 1. Validate All Flows -```bash -$ php artisan flowpipe:validate --all - -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Flow โ”‚ Status โ”‚ Errors โ”‚ Warnings โ”‚ -โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค -โ”‚ ValidationDemo โ”‚ โœ… Valid โ”‚ 0 โ”‚ 0 โ”‚ -โ”‚ InvalidValidationDemo โ”‚ โŒ Invalid โ”‚ 1 โ”‚ 0 โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - -Errors in 'InvalidValidationDemo': - - Missing required field: 'steps' - -Validation failed -``` - -### 2. Validate Specific Flow -```bash -$ php artisan flowpipe:validate --path=validation-demo.yaml - -โœ… Flow 'ValidationDemo' is valid -``` - -### 3. Validate with JSON Output -```bash -$ php artisan flowpipe:validate --all --format=json - -{ - "valid": false, - "flows": [ - { - "name": "ValidationDemo", - "valid": true, - "errors": [], - "warnings": [], - "error_count": 0, - "warning_count": 0 - }, - { - "name": "InvalidValidationDemo", - "valid": false, - "errors": [ - "Missing required field: 'steps'" - ], - "warnings": [], - "error_count": 1, - "warning_count": 0 - } - ], - "summary": { - "total": 2, - "valid": 1, - "invalid": 1, - "errors": 1, - "warnings": 0 - } -} -``` - -## Common Validation Scenarios - -### 1. Development Workflow -```bash -# Create a new flow -php artisan flowpipe:make-flow NewUserFlow - -# Edit the flow definition -# ... make changes to flow_definitions/new_user_flow.yaml ... - -# Validate the flow -php artisan flowpipe:validate --path=new_user_flow.yaml - -# If valid, test the flow -php artisan flowpipe:run NewUserFlow -``` - -### 2. CI/CD Integration -```bash -# In your CI pipeline -php artisan flowpipe:validate --all --format=json > validation-results.json - -# Check exit code -if [ $? -eq 0 ]; then - echo "All flows are valid" -else - echo "Some flows have errors" - exit 1 -fi -``` - -### 3. Pre-deployment Validation -```bash -# Validate all flows before deployment -php artisan flowpipe:validate --all - -# Export documentation for valid flows -php artisan flowpipe:list | tail -n +3 | while read flow; do - if php artisan flowpipe:validate --path="$flow.yaml" > /dev/null 2>&1; then - php artisan flowpipe:export "$flow" --format=md --output="docs/flows/$flow.md" - fi -done -``` - -## Best Practices - -1. **Always validate before deployment**: Include validation in your CI/CD pipeline -2. **Use descriptive error messages**: The validator provides detailed error descriptions -3. **Fix errors systematically**: Address structural issues before reference issues -4. **Test with JSON output**: Use JSON format for automated processing -5. **Document validation results**: Keep records of validation outcomes - -## Validation Rules Summary - -- **Required fields**: `flow`, `steps` -- **Step types**: `action`, `closure`, `step`, `condition`, `group`, `nested` -- **Condition operators**: `equals`, `contains`, `greater_than`, `less_than`, `in` -- **Flow names**: Must start with letter/underscore, contain only alphanumeric, underscore, hyphen -- **References**: Groups and step classes must exist -- **YAML syntax**: Must be valid YAML format - -## Integration with Other Commands - -The validation command works seamlessly with other Flowpipe commands: - -```bash -# Generate, validate, and run -php artisan flowpipe:make-flow TestFlow -php artisan flowpipe:validate --path=test_flow.yaml -php artisan flowpipe:run TestFlow - -# Export only valid flows -php artisan flowpipe:validate --all --format=json | jq -r '.flows[] | select(.valid == true) | .name' | while read flow; do - php artisan flowpipe:export "$flow" --format=mermaid -done -``` \ No newline at end of file diff --git a/examples/validation-examples.md b/examples/validation-examples.md deleted file mode 100644 index 689bc93..0000000 --- a/examples/validation-examples.md +++ /dev/null @@ -1,255 +0,0 @@ -# YAML Validation Command Usage Examples - -This document shows how to use the `flowpipe:validate` command to validate YAML flow definitions. - -## Basic Usage - -### Validate All Flows - -```bash -php artisan flowpipe:validate --all -``` - -Sample output: -``` -Validating all flow definitions... - -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Flow โ”‚ Status โ”‚ Errors โ”‚ Warnings โ”‚ -โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค -โ”‚ UserProcessingFlow โ”‚ โœ… Valid โ”‚ 0 โ”‚ 0 โ”‚ -โ”‚ PaymentFlow โ”‚ โœ… Valid โ”‚ 0 โ”‚ 1 โ”‚ -โ”‚ InvalidFlow โ”‚ โŒ Invalid โ”‚ 3 โ”‚ 0 โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - -Errors in 'InvalidFlow': - - Step 1: Unsupported step type 'invalid_type' - - Step 2: Closure step missing 'action' field - - Step 3: Group 'non_existent_group' not found - -Summary: - Total flows: 3 - Valid flows: 2 - Invalid flows: 1 - Total errors: 3 - Total warnings: 1 -``` - -### Validate Single Flow - -```bash -php artisan flowpipe:validate UserProcessingFlow -``` - -Sample output: -``` -Validating flow: UserProcessingFlow -โœ… Flow 'UserProcessingFlow' is valid -``` - -### Validate with Errors - -```bash -php artisan flowpipe:validate InvalidFlow -``` - -Sample output: -``` -Validating flow: InvalidFlow -โŒ Flow 'InvalidFlow' has errors: - - Step 1: Unsupported step type 'invalid_type' - - Step 2: Closure step missing 'action' field - - Step 3: Group 'non_existent_group' not found -``` - -## Advanced Usage - -### JSON Output - -```bash -php artisan flowpipe:validate --all --format=json -``` - -Sample output: -```json -{ - "summary": { - "total_flows": 3, - "valid_flows": 2, - "invalid_flows": 1, - "total_errors": 3, - "total_warnings": 1 - }, - "results": [ - { - "flow": "UserProcessingFlow", - "valid": true, - "errors": [], - "warnings": [] - }, - { - "flow": "PaymentFlow", - "valid": true, - "errors": [], - "warnings": [ - "Step 3: Consider using more specific error handling" - ] - }, - { - "flow": "InvalidFlow", - "valid": false, - "errors": [ - "Step 1: Unsupported step type 'invalid_type'", - "Step 2: Closure step missing 'action' field", - "Step 3: Group 'non_existent_group' not found" - ], - "warnings": [] - } - ] -} -``` - -### Custom Path - -```bash -php artisan flowpipe:validate --all --path=custom/flow/definitions -``` - -## Common Validation Errors - -### 1. Missing Required Fields - -**Error**: Missing required field: 'flow' -**Cause**: YAML file doesn't have a `flow` field -**Fix**: Add `flow: YourFlowName` to the YAML file - -### 2. Invalid Step Types - -**Error**: Unsupported step type 'custom_step' -**Cause**: Using a step type that's not supported -**Fix**: Use one of: `closure`, `step`, `condition`, `group`, `nested` - -### 3. Missing Step Fields - -**Error**: Closure step missing 'action' field -**Cause**: `closure` step without `action` field -**Fix**: Add `action: your_action_name` to the closure step - -### 4. Invalid Operators - -**Error**: Unsupported operator 'not_equal' -**Cause**: Using an unsupported condition operator -**Fix**: Use one of: `equals`, `contains`, `greater_than`, `less_than`, `in` - -### 5. Missing References - -**Error**: Group 'user_validation' not found -**Cause**: Referencing a group that doesn't exist -**Fix**: Define the group or check the group name - -### 6. YAML Syntax Errors - -**Error**: YAML syntax error: found character that cannot start any token -**Cause**: Invalid YAML syntax -**Fix**: Check YAML formatting, indentation, and special characters - -## Integration with CI/CD - -### Exit Codes - -- `0`: All flows are valid -- `1`: One or more flows have errors - -### Usage in CI Pipeline - -```yaml -# .github/workflows/validate-flows.yml -name: Validate Flow Definitions - -on: [push, pull_request] - -jobs: - validate-flows: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.3' - - - name: Install dependencies - run: composer install --no-dev --optimize-autoloader - - - name: Validate flow definitions - run: php artisan flowpipe:validate --all --format=json -``` - -### Usage in Scripts - -```bash -#!/bin/bash -# validate-flows.sh - -echo "Validating flow definitions..." - -if php artisan flowpipe:validate --all; then - echo "โœ… All flows are valid" - exit 0 -else - echo "โŒ Some flows have errors" - exit 1 -fi -``` - -## Best Practices - -1. **Always validate before deployment** -2. **Use meaningful flow names** (start with letter/underscore) -3. **Test group references** before using them -4. **Keep YAML syntax clean** with proper indentation -5. **Use descriptive error messages** in custom steps -6. **Validate in CI/CD pipelines** to catch errors early - -## Example Valid Flow - -```yaml -flow: UserRegistrationFlow -description: Process new user registration -send: - name: "John Doe" - email: "john@example.com" - password: "securepass123" - -steps: - - type: group - name: user-validation - - - type: condition - condition: - field: email - operator: contains - value: "@" - step: - type: closure - action: send_welcome_email - - - type: nested - steps: - - type: closure - action: hash_password - - type: closure - action: create_user_record - - - type: group - name: user-notifications -``` - -This flow would validate successfully because: -- It has a valid flow name -- All step types are supported -- Required fields are present -- Condition operator is valid -- Nested structure is proper