*/ private array $condition_registry = array(); /** * @var array */ private array $action_registry = array(); /** * Constructor. * * @param WorkflowRepository|null $repository Workflow repository. * @param WorkflowQueue|null $queue Workflow queue. */ public function __construct( ?WorkflowRepository $repository = null, ?WorkflowQueue $queue = null ) { $this->repository = $repository ?? new WorkflowRepository(); $this->queue = $queue ?? new WorkflowQueue(); $this->queue->setWorkflowEngine( $this ); $this->registerDefaultActions(); $this->registerDefaultConditions(); } /** * Register default action handlers. * * @return void */ private function registerDefaultActions(): void { $actions = Actions::getActions(); foreach ( $actions as $type => $action ) { $this->registerAction( $type, new $action['action_class']($this) ); } } private function registerDefaultConditions(): void { $variables = Variables::getVariables(); foreach ($variables as $key => $variable) { if (empty($variable['condition'])) { continue; } $conditionClass = $variable['condition']; $condition = new $conditionClass(); if ( ! $condition instanceof ConditionInterface ) { continue; } $this->registerCondition( $key, $condition ); } } /** * Register a condition type. * * @param string $type Condition type. * @param ConditionInterface $condition Condition instance. * @return void */ public function registerCondition( string $type, ConditionInterface $condition ): void { $this->condition_registry[ $type ] = $condition; } /** * Register an action type. * * @param string $type Action type. * @param object $action Action instance (must have execute method). * @return void */ public function registerAction( string $type, object $action ): void { $this->action_registry[ $type ] = $action; } /** * Get workflows by trigger. * * @param string $trigger Trigger type. * @return Workflow[] */ public function getWorkflowsByTrigger( string $trigger ): array { return $this->repository->findByTrigger( $trigger ); } /** * Evaluate workflow conditions against context. * * @param Workflow $workflow Workflow. * @param array $context Context data. * @return bool */ public function evaluateConditions( Workflow $workflow, array $context ): bool { $conditions = $workflow->getConditions(); if ( empty( $conditions ) ) { return true; } foreach ( $conditions as $condition ) { // @var array $condition $variable = trim($condition['variable'] ?? ''); if ( empty($variable) ) { continue; } $handler = $this->condition_registry[ $variable ] ?? null; if ( null === $handler ) { continue; } try { if ( ! $handler->evaluate( $condition, $context ) ) { return false; } } catch (\Exception $e) { Log::error('WorkflowEngine: error evaluating condition', array( 'condition' => $condition, 'context' => $context, 'error' => $e->getMessage(), )); return false; } } return true; } /** * Enqueue a workflow for execution (possibly with delay from first step). * * @param Workflow $workflow Workflow. * @param array $context Context data. * @return int|false Queue job ID or false. */ public function enqueueWorkflow( Workflow $workflow, array $context ) { $steps = $workflow->getSteps(); $run_at = current_time( 'mysql' ); /* if ( ! empty( $steps ) ) { $first_step = $steps[0]; $delay = isset( $first_step['delay'] ) ? (int) $first_step['delay'] : 0; if ( $delay > 0 ) { $run_at = gmdate( 'Y-m-d H:i:s', strtotime( $run_at ) + $delay ); //@TODO maybe we should make this first step as done? } } */ return $this->queue->enqueue( $workflow->getId(), $context, $run_at ); } /** * Execute a workflow immediately. * * @param WorkflowJob $job Workflow job. * @return void */ public function executeWorkflow( WorkflowJob $job ): void { $workflow = $this->repository->findById( $job->getLocalWorkflowId() ); if ( null === $workflow || ! $workflow->isActive() ) { return; } $context = $job->getContext(); $start_at_step = (int) ( $context['start_at_step'] ?? 0 ); unset( $context['start_at_step'] ); $steps = $workflow->getSteps(); $this->executeSteps( array_values( $steps ), $context, $job, $start_at_step ); } /** * Execute a list of workflow steps. * * @param array $steps Steps to execute. * @param array $context Workflow context. * @param WorkflowJob $job Workflow job. * @param int $start_index Index to start from (for resume). * @return void */ private function executeSteps( array $steps, array $context, WorkflowJob $job, int $start_index = 0 ): void { $current_context = $context; foreach ( $steps as $index => $step ) { if ( $index < $start_index ) { continue; } $action_type = $step['type'] ?? ''; if ( '' === $action_type ) { continue; } $action = $this->action_registry[ $action_type ] ?? null; if ( null === $action || ! method_exists( $action, 'execute' ) ) { continue; } $current_context['step_index'] = $index; $executed = $action->execute( $step, $current_context, $job ); if ( 'wait' === $action_type && true === $executed ) { throw new WorkflowPausedException(); } } } /** * Get the workflow queue. * * @return WorkflowQueue */ public function getQueue(): WorkflowQueue { return $this->queue; } }