Skip to content

Commit 166de46

Browse files
committed
wip
1 parent 14ae146 commit 166de46

32 files changed

+5319
-390
lines changed

src/Actions/BaseAction.php

Lines changed: 316 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,82 +6,386 @@
66
use SolutionForest\WorkflowMastery\Contracts\WorkflowAction;
77
use SolutionForest\WorkflowMastery\Core\ActionResult;
88
use SolutionForest\WorkflowMastery\Core\WorkflowContext;
9+
use SolutionForest\WorkflowMastery\Exceptions\StepExecutionException;
910

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+
*/
1077
abstract class BaseAction implements WorkflowAction
1178
{
79+
/** @var array<string, mixed> Action-specific configuration parameters */
1280
protected array $config;
1381

82+
/**
83+
* Create a new base action with optional configuration.
84+
*
85+
* @param array<string, mixed> $config Action-specific configuration
86+
*/
1487
public function __construct(array $config = [])
1588
{
1689
$this->config = $config;
1790
}
1891

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+
*/
19128
public function execute(WorkflowContext $context): ActionResult
20129
{
21130
Log::info('Executing action', [
22131
'action' => static::class,
132+
'action_name' => $this->getName(),
23133
'workflow_id' => $context->getWorkflowId(),
24-
'step_id' => $context->getCurrentStepId(),
134+
'step_id' => $context->getStepId(),
135+
'config' => $this->config,
25136
]);
26137

27138
try {
28-
// Validate prerequisites
139+
// Validate prerequisites before execution
29140
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);
31155
}
32156

33-
// Execute business logic
157+
// Execute the action's business logic
34158
$result = $this->doExecute($context);
35159

36-
// Log success
160+
// Log successful completion with result data
37161
Log::info('Action completed successfully', [
38162
'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(),
40168
]);
41169

42170
return $result;
43171

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;
44183
} 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', [
46187
'action' => static::class,
188+
'workflow_id' => $context->getWorkflowId(),
189+
'step_id' => $context->getStepId(),
47190
'error' => $e->getMessage(),
191+
'exception_class' => get_class($e),
48192
'trace' => $e->getTraceAsString(),
49193
]);
50194

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+
);
52208
}
53209
}
54210

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+
*/
55244
public function canExecute(WorkflowContext $context): bool
56245
{
57246
return true; // Default implementation allows execution
58247
}
59248

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+
*/
60266
public function getName(): string
61267
{
62268
return class_basename(static::class);
63269
}
64270

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+
*/
65288
public function getDescription(): string
66289
{
67-
return 'Base workflow action';
290+
return 'Base workflow action implementation with logging and error handling';
68291
}
69292

70293
/**
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+
* ```
72337
*/
73338
abstract protected function doExecute(WorkflowContext $context): ActionResult;
74339

75340
/**
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+
* ```
77362
*/
78363
protected function getConfig(string $key, $default = null)
79364
{
80365
return data_get($this->config, $key, $default);
81366
}
82367

83368
/**
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+
* ```
85389
*/
86390
protected function getAllConfig(): array
87391
{

0 commit comments

Comments
 (0)