Skip to content

Commit d016bad

Browse files
committed
wip
1 parent c5f6a82 commit d016bad

File tree

4 files changed

+124
-14
lines changed

4 files changed

+124
-14
lines changed

src/Actions/EmailAction.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
namespace SolutionForest\WorkflowEngine\Actions;
4+
5+
use SolutionForest\WorkflowEngine\Contracts\WorkflowAction;
6+
use SolutionForest\WorkflowEngine\Core\ActionResult;
7+
use SolutionForest\WorkflowEngine\Core\WorkflowContext;
8+
9+
class EmailAction implements WorkflowAction
10+
{
11+
public function execute(WorkflowContext $context): ActionResult
12+
{
13+
$template = $context->getConfig('template', 'default');
14+
$to = $context->getConfig('to', '');
15+
$subject = $context->getConfig('subject', '');
16+
$data = $context->getConfig('data', []);
17+
18+
// Mock email sending - in real implementation this would send actual emails
19+
$emailData = [
20+
'template' => $template,
21+
'to' => $to,
22+
'subject' => $subject,
23+
'data' => $data,
24+
'sent_at' => date('Y-m-d H:i:s'),
25+
'status' => 'sent',
26+
];
27+
28+
return ActionResult::success(['email_sent' => $emailData]);
29+
}
30+
31+
public function canExecute(WorkflowContext $context): bool
32+
{
33+
return ! empty($context->getConfig('to'));
34+
}
35+
36+
public function getName(): string
37+
{
38+
return 'Send Email';
39+
}
40+
41+
public function getDescription(): string
42+
{
43+
return 'Sends an email using the specified template and configuration';
44+
}
45+
}

src/Core/WorkflowBuilder.php

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ public function addStep(
167167
string $id,
168168
string|WorkflowAction $action,
169169
array $config = [],
170-
?int $timeout = null,
170+
string|int|null $timeout = null,
171171
int $retryAttempts = 0
172172
): self {
173173
if (empty(trim($id))) {
@@ -178,8 +178,19 @@ public function addStep(
178178
throw InvalidWorkflowDefinitionException::invalidRetryAttempts($retryAttempts);
179179
}
180180

181-
if ($timeout !== null && $timeout <= 0) {
182-
throw InvalidWorkflowDefinitionException::invalidTimeout($timeout);
181+
// Validate timeout format if provided
182+
if ($timeout !== null) {
183+
if (is_string($timeout)) {
184+
// Validate the string format and convert to ensure it's valid
185+
$timeoutSeconds = $this->parseTimeoutString($timeout);
186+
if ($timeoutSeconds <= 0) {
187+
throw InvalidWorkflowDefinitionException::invalidTimeout($timeout);
188+
}
189+
} else {
190+
if ($timeout <= 0) {
191+
throw InvalidWorkflowDefinitionException::invalidTimeout($timeout);
192+
}
193+
}
183194
}
184195

185196
// Check for duplicate step IDs
@@ -193,7 +204,7 @@ public function addStep(
193204
'id' => $id,
194205
'action' => is_string($action) ? $action : $action::class,
195206
'config' => $config,
196-
'timeout' => $timeout,
207+
'timeout' => $timeout, // Store original format, not converted
197208
'retry_attempts' => $retryAttempts,
198209
];
199210

@@ -217,7 +228,7 @@ public function addStep(
217228
public function startWith(
218229
string|WorkflowAction $action,
219230
array $config = [],
220-
?int $timeout = null,
231+
string|int|null $timeout = null,
221232
int $retryAttempts = 0
222233
): self {
223234
$stepId = 'step_'.(count($this->steps) + 1);
@@ -242,7 +253,7 @@ public function startWith(
242253
public function then(
243254
string|WorkflowAction $action,
244255
array $config = [],
245-
?int $timeout = null,
256+
string|int|null $timeout = null,
246257
int $retryAttempts = 0
247258
): self {
248259
$stepId = 'step_'.(count($this->steps) + 1);
@@ -459,11 +470,21 @@ public function build(): WorkflowDefinition
459470
// Convert builder format to Step objects
460471
$steps = [];
461472
foreach ($this->steps as $stepData) {
473+
// Convert timeout to string format for Step object - keep original format if string, convert integer to string
474+
$timeoutString = null;
475+
if ($stepData['timeout'] !== null) {
476+
if (is_string($stepData['timeout'])) {
477+
$timeoutString = $stepData['timeout']; // Keep original string format
478+
} else {
479+
$timeoutString = (string) $stepData['timeout']; // Convert integer to string
480+
}
481+
}
482+
462483
$steps[] = new Step(
463484
id: $stepData['id'],
464485
actionClass: $stepData['action'],
465486
config: $stepData['config'],
466-
timeout: $stepData['timeout'] ? (string) $stepData['timeout'] : null,
487+
timeout: $timeoutString,
467488
retryAttempts: $stepData['retry_attempts'],
468489
conditions: isset($stepData['condition']) ? [$stepData['condition']] : []
469490
);
@@ -498,6 +519,31 @@ public static function quick(): QuickWorkflowBuilder
498519
{
499520
return new QuickWorkflowBuilder;
500521
}
522+
523+
/**
524+
* Convert timeout string to seconds.
525+
*
526+
* @param string $timeout Timeout string like '30s', '5m', '2h', '1d'
527+
* @return int Timeout in seconds
528+
*
529+
* @throws InvalidWorkflowDefinitionException If format is invalid
530+
*/
531+
private function parseTimeoutString(string $timeout): int
532+
{
533+
if (! preg_match('/^(\d+)([smhd])$/', $timeout, $matches)) {
534+
throw InvalidWorkflowDefinitionException::invalidTimeout($timeout);
535+
}
536+
537+
$value = (int) $matches[1];
538+
$unit = $matches[2];
539+
540+
return match ($unit) {
541+
's' => $value,
542+
'm' => $value * 60,
543+
'h' => $value * 3600,
544+
'd' => $value * 86400,
545+
};
546+
}
501547
}
502548

503549
/**

src/Core/WorkflowContext.php

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,13 +88,30 @@ public function getStepId(): string
8888
}
8989

9090
/**
91-
* Get all workflow execution data.
91+
* Get workflow execution data.
9292
*
93-
* @return array<string, mixed> Complete data array
93+
* When called without parameters, returns all data.
94+
* When called with a key parameter, returns the specific data value (supports dot notation).
95+
*
96+
* @param string|null $key Data key (supports dot notation for nested access), or null for all data
97+
* @param mixed $default Default value to return if key doesn't exist
98+
* @return mixed Complete data array when key is null, or specific value when key is provided
99+
*
100+
* @example
101+
* ```php
102+
* $allData = $context->getData();
103+
* $user = $context->getData('user');
104+
* $userName = $context->getData('user.name');
105+
* $email = $context->getData('user.email', 'unknown@example.com');
106+
* ```
94107
*/
95-
public function getData(): array
108+
public function getData(?string $key = null, mixed $default = null): mixed
96109
{
97-
return $this->data;
110+
if ($key === null) {
111+
return $this->data;
112+
}
113+
114+
return data_get($this->data, $key, $default);
98115
}
99116

100117
/**

src/Exceptions/InvalidWorkflowDefinitionException.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,15 +148,17 @@ public static function invalidRetryAttempts(int $attempts): static
148148
/**
149149
* Create exception for invalid timeout.
150150
*
151-
* @param int|null $timeout The invalid timeout value
151+
* @param string|int|null $timeout The invalid timeout value
152152
*/
153-
public static function invalidTimeout(?int $timeout): static
153+
public static function invalidTimeout(mixed $timeout): static
154154
{
155+
$timeoutStr = $timeout === null ? 'null' : (string) $timeout;
155156
return new self(
156-
message: "Invalid timeout: {$timeout}. Timeout must be a positive integer or null.",
157+
message: "Invalid timeout: {$timeoutStr}. Timeout must be a positive integer, valid time string (e.g., '30s', '5m'), or null.",
157158
definition: ['provided_timeout' => $timeout],
158159
validationErrors: [
159160
'Use a positive integer for timeout in seconds',
161+
'Use a valid time string format: number followed by s/m/h/d (e.g., "30s", "5m", "2h", "1d")',
160162
'Use null for no timeout limit',
161163
'Consider reasonable timeouts: 30s for quick operations, 300s for complex tasks',
162164
]

0 commit comments

Comments
 (0)