|
6 | 6 | use SolutionForest\WorkflowMastery\Contracts\WorkflowAction; |
7 | 7 | use SolutionForest\WorkflowMastery\Core\ActionResult; |
8 | 8 | use SolutionForest\WorkflowMastery\Core\WorkflowContext; |
| 9 | +use SolutionForest\WorkflowMastery\Exceptions\StepExecutionException; |
9 | 10 |
|
| 11 | +/** |
| 12 | + * Base implementation for workflow actions with common functionality. |
| 13 | + * |
| 14 | + * BaseAction provides a convenient foundation for implementing workflow actions |
| 15 | + * with built-in logging, error handling, configuration management, and lifecycle |
| 16 | + * methods. It implements the template method pattern to provide consistent |
| 17 | + * action execution flow while allowing customization of specific logic. |
| 18 | + * |
| 19 | + * ## Features Provided |
| 20 | + * - **Automatic Logging**: Comprehensive logging of action execution and errors |
| 21 | + * - **Error Handling**: Consistent exception handling and error reporting |
| 22 | + * - **Configuration Management**: Easy access to action configuration |
| 23 | + * - **Template Method**: Structured execution flow with customization points |
| 24 | + * - **Validation**: Built-in prerequisite checking before execution |
| 25 | + * |
| 26 | + * ## Usage Examples |
| 27 | + * |
| 28 | + * ### Simple Action Implementation |
| 29 | + * ```php |
| 30 | + * class UpdateUserStatusAction extends BaseAction |
| 31 | + * { |
| 32 | + * protected function doExecute(WorkflowContext $context): ActionResult |
| 33 | + * { |
| 34 | + * $userId = data_get($context->getData(), 'user.id'); |
| 35 | + * $status = $this->getConfig('status', 'active'); |
| 36 | + * |
| 37 | + * User::where('id', $userId)->update(['status' => $status]); |
| 38 | + * |
| 39 | + * return ActionResult::success(['user_id' => $userId, 'status' => $status]); |
| 40 | + * } |
| 41 | + * |
| 42 | + * public function canExecute(WorkflowContext $context): bool |
| 43 | + * { |
| 44 | + * return data_get($context->getData(), 'user.id') !== null; |
| 45 | + * } |
| 46 | + * |
| 47 | + * public function getName(): string |
| 48 | + * { |
| 49 | + * return 'Update User Status'; |
| 50 | + * } |
| 51 | + * } |
| 52 | + * ``` |
| 53 | + * |
| 54 | + * ### Action with Complex Validation |
| 55 | + * ```php |
| 56 | + * class ProcessPaymentAction extends BaseAction |
| 57 | + * { |
| 58 | + * public function canExecute(WorkflowContext $context): bool |
| 59 | + * { |
| 60 | + * $data = $context->getData(); |
| 61 | + * |
| 62 | + * // Check required data |
| 63 | + * if (!data_get($data, 'payment.amount') || !data_get($data, 'payment.method')) { |
| 64 | + * return false; |
| 65 | + * } |
| 66 | + * |
| 67 | + * // Check configuration |
| 68 | + * return !empty($this->getConfig('gateway_key')); |
| 69 | + * } |
| 70 | + * } |
| 71 | + * ``` |
| 72 | + * |
| 73 | + * @see WorkflowAction For the interface definition |
| 74 | + * @see ActionResult For return value structure |
| 75 | + * @see StepExecutionException For error handling patterns |
| 76 | + */ |
10 | 77 | abstract class BaseAction implements WorkflowAction |
11 | 78 | { |
| 79 | + /** @var array<string, mixed> Action-specific configuration parameters */ |
12 | 80 | protected array $config; |
13 | 81 |
|
| 82 | + /** |
| 83 | + * Create a new base action with optional configuration. |
| 84 | + * |
| 85 | + * @param array<string, mixed> $config Action-specific configuration |
| 86 | + */ |
14 | 87 | public function __construct(array $config = []) |
15 | 88 | { |
16 | 89 | $this->config = $config; |
17 | 90 | } |
18 | 91 |
|
| 92 | + /** |
| 93 | + * Execute the workflow action with comprehensive logging and error handling. |
| 94 | + * |
| 95 | + * This method implements the template method pattern, providing a consistent |
| 96 | + * execution flow while allowing customization through the abstract doExecute |
| 97 | + * method and optional canExecute validation. |
| 98 | + * |
| 99 | + * ## Execution Flow |
| 100 | + * 1. **Logging**: Log action start with context information |
| 101 | + * 2. **Validation**: Check prerequisites via canExecute() |
| 102 | + * 3. **Execution**: Run business logic via doExecute() |
| 103 | + * 4. **Success Logging**: Log successful completion with results |
| 104 | + * 5. **Error Handling**: Catch and log any exceptions |
| 105 | + * |
| 106 | + * ## Error Handling |
| 107 | + * All exceptions are caught and converted to failed ActionResults. |
| 108 | + * For custom error handling, override this method or throw |
| 109 | + * StepExecutionException with specific context. |
| 110 | + * |
| 111 | + * @param WorkflowContext $context The current workflow execution context |
| 112 | + * @return ActionResult Success or failure result with data and messages |
| 113 | + * |
| 114 | + * @throws StepExecutionException When action execution fails |
| 115 | + * |
| 116 | + * @example Basic execution flow |
| 117 | + * ```php |
| 118 | + * $action = new MyAction(['config' => 'value']); |
| 119 | + * $result = $action->execute($context); |
| 120 | + * |
| 121 | + * if ($result->isSuccess()) { |
| 122 | + * echo "Action completed: " . json_encode($result->getData()); |
| 123 | + * } else { |
| 124 | + * echo "Action failed: " . $result->getMessage(); |
| 125 | + * } |
| 126 | + * ``` |
| 127 | + */ |
19 | 128 | public function execute(WorkflowContext $context): ActionResult |
20 | 129 | { |
21 | 130 | Log::info('Executing action', [ |
22 | 131 | 'action' => static::class, |
| 132 | + 'action_name' => $this->getName(), |
23 | 133 | 'workflow_id' => $context->getWorkflowId(), |
24 | | - 'step_id' => $context->getCurrentStepId(), |
| 134 | + 'step_id' => $context->getStepId(), |
| 135 | + 'config' => $this->config, |
25 | 136 | ]); |
26 | 137 |
|
27 | 138 | try { |
28 | | - // Validate prerequisites |
| 139 | + // Validate prerequisites before execution |
29 | 140 | if (! $this->canExecute($context)) { |
30 | | - return ActionResult::failure('Prerequisites not met'); |
| 141 | + $message = sprintf( |
| 142 | + 'Action prerequisites not met for %s in workflow %s step %s', |
| 143 | + $this->getName(), |
| 144 | + $context->getWorkflowId(), |
| 145 | + $context->getStepId() |
| 146 | + ); |
| 147 | + |
| 148 | + Log::warning('Action prerequisites failed', [ |
| 149 | + 'action' => static::class, |
| 150 | + 'workflow_id' => $context->getWorkflowId(), |
| 151 | + 'step_id' => $context->getStepId(), |
| 152 | + ]); |
| 153 | + |
| 154 | + return ActionResult::failure($message); |
31 | 155 | } |
32 | 156 |
|
33 | | - // Execute business logic |
| 157 | + // Execute the action's business logic |
34 | 158 | $result = $this->doExecute($context); |
35 | 159 |
|
36 | | - // Log success |
| 160 | + // Log successful completion with result data |
37 | 161 | Log::info('Action completed successfully', [ |
38 | 162 | 'action' => static::class, |
39 | | - 'result' => $result->getData(), |
| 163 | + 'action_name' => $this->getName(), |
| 164 | + 'workflow_id' => $context->getWorkflowId(), |
| 165 | + 'step_id' => $context->getStepId(), |
| 166 | + 'success' => $result->isSuccess(), |
| 167 | + 'result_data' => $result->getData(), |
40 | 168 | ]); |
41 | 169 |
|
42 | 170 | return $result; |
43 | 171 |
|
| 172 | + } catch (StepExecutionException $e) { |
| 173 | + // Re-throw StepExecutionException to preserve context |
| 174 | + Log::error('Action failed with step execution exception', [ |
| 175 | + 'action' => static::class, |
| 176 | + 'workflow_id' => $context->getWorkflowId(), |
| 177 | + 'step_id' => $context->getStepId(), |
| 178 | + 'error' => $e->getMessage(), |
| 179 | + 'context' => $e->getContext(), |
| 180 | + ]); |
| 181 | + |
| 182 | + throw $e; |
44 | 183 | } catch (\Exception $e) { |
45 | | - Log::error('Action failed', [ |
| 184 | + // Log and return failure result for general exceptions |
| 185 | + // The Executor will convert this to a StepExecutionException with Step context |
| 186 | + Log::error('Action failed with unexpected exception', [ |
46 | 187 | 'action' => static::class, |
| 188 | + 'workflow_id' => $context->getWorkflowId(), |
| 189 | + 'step_id' => $context->getStepId(), |
47 | 190 | 'error' => $e->getMessage(), |
| 191 | + 'exception_class' => get_class($e), |
48 | 192 | 'trace' => $e->getTraceAsString(), |
49 | 193 | ]); |
50 | 194 |
|
51 | | - return ActionResult::failure($e->getMessage()); |
| 195 | + return ActionResult::failure( |
| 196 | + sprintf( |
| 197 | + 'Action %s failed: %s', |
| 198 | + $this->getName(), |
| 199 | + $e->getMessage() |
| 200 | + ), |
| 201 | + [ |
| 202 | + 'exception_class' => get_class($e), |
| 203 | + 'exception_code' => $e->getCode(), |
| 204 | + 'exception_file' => $e->getFile(), |
| 205 | + 'exception_line' => $e->getLine(), |
| 206 | + ] |
| 207 | + ); |
52 | 208 | } |
53 | 209 | } |
54 | 210 |
|
| 211 | + /** |
| 212 | + * Check if the action can be executed with the given context. |
| 213 | + * |
| 214 | + * This method provides a validation hook before action execution. |
| 215 | + * Override this method to implement custom validation logic, such as |
| 216 | + * checking required data fields, external service availability, or |
| 217 | + * action-specific prerequisites. |
| 218 | + * |
| 219 | + * ## Validation Examples |
| 220 | + * - **Data Validation**: Check required fields in context data |
| 221 | + * - **Configuration**: Verify required configuration values |
| 222 | + * - **External Dependencies**: Test connectivity to external services |
| 223 | + * - **Business Rules**: Apply domain-specific validation logic |
| 224 | + * |
| 225 | + * @param WorkflowContext $context The current workflow execution context |
| 226 | + * @return bool True if the action can be executed, false otherwise |
| 227 | + * |
| 228 | + * @example Data validation |
| 229 | + * ```php |
| 230 | + * public function canExecute(WorkflowContext $context): bool |
| 231 | + * { |
| 232 | + * $data = $context->getData(); |
| 233 | + * |
| 234 | + * // Check required fields |
| 235 | + * if (!data_get($data, 'user.id') || !data_get($data, 'email')) { |
| 236 | + * return false; |
| 237 | + * } |
| 238 | + * |
| 239 | + * // Check configuration |
| 240 | + * return !empty($this->getConfig('api_key')); |
| 241 | + * } |
| 242 | + * ``` |
| 243 | + */ |
55 | 244 | public function canExecute(WorkflowContext $context): bool |
56 | 245 | { |
57 | 246 | return true; // Default implementation allows execution |
58 | 247 | } |
59 | 248 |
|
| 249 | + /** |
| 250 | + * Get a human-readable name for this action. |
| 251 | + * |
| 252 | + * Override this method to provide a descriptive name for the action |
| 253 | + * that will be used in logging, debugging, and user interfaces. |
| 254 | + * The default implementation returns the class name without namespace. |
| 255 | + * |
| 256 | + * @return string The action name |
| 257 | + * |
| 258 | + * @example Custom action name |
| 259 | + * ```php |
| 260 | + * public function getName(): string |
| 261 | + * { |
| 262 | + * return 'Send Welcome Email'; |
| 263 | + * } |
| 264 | + * ``` |
| 265 | + */ |
60 | 266 | public function getName(): string |
61 | 267 | { |
62 | 268 | return class_basename(static::class); |
63 | 269 | } |
64 | 270 |
|
| 271 | + /** |
| 272 | + * Get a detailed description of what this action does. |
| 273 | + * |
| 274 | + * Override this method to provide a comprehensive description of the |
| 275 | + * action's purpose, behavior, and effects. This is useful for |
| 276 | + * documentation, debugging, and workflow visualization tools. |
| 277 | + * |
| 278 | + * @return string The action description |
| 279 | + * |
| 280 | + * @example Detailed description |
| 281 | + * ```php |
| 282 | + * public function getDescription(): string |
| 283 | + * { |
| 284 | + * return 'Sends a personalized welcome email to new users with account setup instructions and verification link.'; |
| 285 | + * } |
| 286 | + * ``` |
| 287 | + */ |
65 | 288 | public function getDescription(): string |
66 | 289 | { |
67 | | - return 'Base workflow action'; |
| 290 | + return 'Base workflow action implementation with logging and error handling'; |
68 | 291 | } |
69 | 292 |
|
70 | 293 | /** |
71 | | - * Implement the actual action logic in this method |
| 294 | + * Implement the actual action logic in this method. |
| 295 | + * |
| 296 | + * This is the main method where action-specific business logic should be |
| 297 | + * implemented. It will be called by the execute() method after validation |
| 298 | + * and logging setup. The method should return an ActionResult indicating |
| 299 | + * success or failure. |
| 300 | + * |
| 301 | + * ## Implementation Guidelines |
| 302 | + * - **Return ActionResult**: Always return success() or failure() result |
| 303 | + * - **Exception Handling**: Let exceptions bubble up for consistent logging |
| 304 | + * - **Data Access**: Use context data and action configuration |
| 305 | + * - **Side Effects**: Perform the action's business operations |
| 306 | + * |
| 307 | + * @param WorkflowContext $context The current workflow execution context |
| 308 | + * @return ActionResult The result of the action execution |
| 309 | + * |
| 310 | + * @throws \Exception Any exceptions during action execution |
| 311 | + * |
| 312 | + * @example Implementation pattern |
| 313 | + * ```php |
| 314 | + * protected function doExecute(WorkflowContext $context): ActionResult |
| 315 | + * { |
| 316 | + * // Get data from context |
| 317 | + * $userId = data_get($context->getData(), 'user.id'); |
| 318 | + * $email = data_get($context->getData(), 'user.email'); |
| 319 | + * |
| 320 | + * // Get configuration |
| 321 | + * $template = $this->getConfig('email_template', 'welcome'); |
| 322 | + * |
| 323 | + * // Perform business logic |
| 324 | + * $result = EmailService::send($email, $template, ['user_id' => $userId]); |
| 325 | + * |
| 326 | + * // Return appropriate result |
| 327 | + * if ($result['sent']) { |
| 328 | + * return ActionResult::success([ |
| 329 | + * 'email_id' => $result['id'], |
| 330 | + * 'sent_at' => now()->toISOString() |
| 331 | + * ]); |
| 332 | + * } else { |
| 333 | + * return ActionResult::failure('Failed to send email: ' . $result['error']); |
| 334 | + * } |
| 335 | + * } |
| 336 | + * ``` |
72 | 337 | */ |
73 | 338 | abstract protected function doExecute(WorkflowContext $context): ActionResult; |
74 | 339 |
|
75 | 340 | /** |
76 | | - * Get configuration value |
| 341 | + * Get a configuration value with optional default. |
| 342 | + * |
| 343 | + * Retrieves a value from the action configuration using dot notation. |
| 344 | + * This is a convenience method for accessing action-specific settings |
| 345 | + * that were provided during action instantiation. |
| 346 | + * |
| 347 | + * @param string $key The configuration key (supports dot notation) |
| 348 | + * @param mixed $default The default value if key is not found |
| 349 | + * @return mixed The configuration value or default |
| 350 | + * |
| 351 | + * @example Configuration access |
| 352 | + * ```php |
| 353 | + * // Simple key |
| 354 | + * $apiKey = $this->getConfig('api_key'); |
| 355 | + * |
| 356 | + * // Nested key with dot notation |
| 357 | + * $timeout = $this->getConfig('http.timeout', 30); |
| 358 | + * |
| 359 | + * // Array key with default |
| 360 | + * $retries = $this->getConfig('retry.attempts', 3); |
| 361 | + * ``` |
77 | 362 | */ |
78 | 363 | protected function getConfig(string $key, $default = null) |
79 | 364 | { |
80 | 365 | return data_get($this->config, $key, $default); |
81 | 366 | } |
82 | 367 |
|
83 | 368 | /** |
84 | | - * Get all configuration |
| 369 | + * Get all action configuration. |
| 370 | + * |
| 371 | + * Returns the complete configuration array that was provided |
| 372 | + * during action construction. Useful for debugging or when |
| 373 | + * you need to access multiple configuration values. |
| 374 | + * |
| 375 | + * @return array<string, mixed> The complete configuration array |
| 376 | + * |
| 377 | + * @example Configuration access |
| 378 | + * ```php |
| 379 | + * $config = $this->getAllConfig(); |
| 380 | + * |
| 381 | + * // Log all configuration for debugging |
| 382 | + * Log::debug('Action config', $config); |
| 383 | + * |
| 384 | + * // Check if any configuration was provided |
| 385 | + * if (empty($config)) { |
| 386 | + * // Use default behavior |
| 387 | + * } |
| 388 | + * ``` |
85 | 389 | */ |
86 | 390 | protected function getAllConfig(): array |
87 | 391 | { |
|
0 commit comments