You are here

protected function MaestroEngine::nextStep in Maestro 3.x

Same name and namespace in other branches
  1. 8.2 src/Engine/MaestroEngine.php \Drupal\maestro\Engine\MaestroEngine::nextStep()

NextStep Engine method that determines which is the next step based on the current step and does all assignments as necessary.

Parameters

string $template: The Maestro template.

string $templateTaskID: The machine name of the task.

int $processID: The Maestro Process ID.

int $completionStatus: The completion status being set. Definitions found in maestro.module.

1 call to MaestroEngine::nextStep()
MaestroEngine::cleanQueue in src/Engine/MaestroEngine.php
CleanQueue method This is the core method used by the orchestrator to move the process forward and to determine assignments and next steps.

File

src/Engine/MaestroEngine.php, line 1546

Class

MaestroEngine
Class MaestroEngine.

Namespace

Drupal\maestro\Engine

Code

protected function nextStep($template, $templateTaskID, $processID, $completionStatus) {
  $templateTask = $this
    ->getTemplateTaskByID($template, $templateTaskID);
  $regenerationFlag = FALSE;

  // Nextstep or nextfalsestep is a comma separated string of next task machine names to point to.
  $nextSteps = $templateTask['nextstep'];

  // Completion status tells us to point to the false branch.
  if ($completionStatus == MAESTRO_TASK_COMPLETION_USE_FALSE_BRANCH) {
    $nextSteps = $templateTask['nextfalsestep'];
  }

  // TODO:  In the event we have a feature request for extra completion status codes, at this point we've established the core Maestro status codes and,
  // have altered the next steps to respect the IF task scenario.
  // Is there really any other scenario?  We've never come across this issue.  But this would be the spot in-code to allow for a
  // module invocation to alter the next steps based on completion status.
  if ($nextSteps != '') {
    $taskArray = explode(',', $nextSteps);
    foreach ($taskArray as $taskID) {

      // Determine if this task is already present in this instance of the queue/process combo
      // but first, determine if they're trying to recreate a task that has already been completed
      // this is our chance to do an auto-regeneration
      // we also filter for the tasks not being an OR or AND task.
      $query = \Drupal::entityTypeManager()
        ->getStorage('maestro_queue')
        ->getQuery();

      // Race condition?  what if its complete and not archived, yet a loopback happens?  Leave for now.
      $query
        ->condition('archived', TASK_ARCHIVE_REGEN, '<>')
        ->condition('status', TASK_STATUS_ACTIVE, '<>')
        ->condition('process_id', $processID)
        ->condition('task_id', $taskID)
        ->condition('task_class_name', 'MaestroOr', '<>')
        ->condition('task_class_name', 'MaestroAnd', '<>');
      $entity_ids = $query
        ->execute();
      if (count($entity_ids) == 0) {

        // No regeneration!  this is a straightforward engine carry-on condition
        // in Drupal 7's engine, we had flags to check to see if a task REEEEEEALY wanted to be regenerated.
        // no more.  After 10 years of engine development, we've found that regeneration of all in-prod tasks is the way to go
        // look at the ELSE clause to see the regen.
        $query = \Drupal::entityTypeManager()
          ->getStorage('maestro_queue')
          ->getQuery();

        // We don't need to recreate if this thing is already in the queue.
        $query
          ->condition('archived', '0')
          ->condition('status', TASK_STATUS_ACTIVE)
          ->condition('process_id', $processID)
          ->condition('task_id', $taskID);
        $entity_ids = $query
          ->execute();

        // Means we haven't already created it in this process. avoids mimicking the regen issue.
        if (count($entity_ids) == 0) {
          $queueID = $this
            ->createProductionTask($taskID, $template, $processID);
        }
      }
      else {

        // It is in this area where we are doing a complete loopback over our existing template
        // after years of development experience and creating many business logic templates, we've found that
        // the overwhelming majority (like 99%) of all templates really do a regeneration of all
        // in-production tasks and that people really do want to do a regeneration.
        // Thus the regen flags have been omitted.  Now we just handle everything with status flags and keep the same
        // process ID.
        // The biggest issue are the AND tasks.  We need to know which tasks the AND has pointing to it and keep those
        // tasks hanging around in the queue in either a completed and archived state or in their fully open, executable state.
        // so we have to first find all AND tasks, and then determine who points to them and leave their archive condition alone.
        $noRegenStatusArray = [];

        // So first, search for open AND tasks:
        // Race condition?  what if its complete and not archived, yet a loopback happens?  Leave for now.
        $query = \Drupal::entityTypeManager()
          ->getStorage('maestro_queue')
          ->getQuery();
        $query
          ->condition('archived', '1')
          ->condition('status', '0')
          ->condition('process_id', $processID)
          ->condition('task_class_name', 'MaestroAnd');

        // Going to use these IDs to determine who points to them.
        $andIDs = $query
          ->execute();
        if (is_array($andIDs)) {
          $noRegenStatusArray += $andIDs;
        }
        foreach ($andIDs as $entityID) {

          // Load the entity from the queue.
          $queueRecord = MaestroEngine::getQueueEntryById($entityID);
          $pointers = MaestroEngine::getTaskPointersFromTemplate(MaestroEngine::getTemplateIdFromProcessId($processID), $queueRecord->task_id
            ->getValue());

          // Now we query the queue to add the pointers to the noRegenStatusArray.
          $query = \Drupal::entityQuery('maestro_queue')
            ->accessCheck(FALSE);
          $andMainConditions = $query
            ->andConditionGroup()
            ->condition('process_id', $processID);
          $orConditionGroup = $query
            ->orConditionGroup();
          foreach ($pointers as $taskID) {
            $orConditionGroup
              ->condition('task_id', $taskID);
          }
          $andMainConditions
            ->condition($orConditionGroup);
          $query
            ->condition($andMainConditions);
          $pointerIDs = $query
            ->execute();
          if (is_array($pointerIDs)) {

            // Add the entity IDs to the tasks that point to the AND as those that shouldn't be flagged.
            $noRegenStatusArray += $pointerIDs;
          }
        }

        // now, we have a list of noRegenStatusArray which are entity IDs in the maestro_queue for which we do NOT change the archive flag for.
        $query = \Drupal::entityTypeManager()
          ->getStorage('maestro_queue')
          ->getQuery();
        $query
          ->condition('status', '0', '<>')
          ->condition('process_id', $processID);
        $regenIDs = $query
          ->execute();
        foreach ($regenIDs as $entityID) {

          // Set this queue record to regenerated IF it doesn't exist in the noRegenStatusArray.
          if (array_search($entityID, $noRegenStatusArray) === FALSE) {
            $queueRecord = MaestroEngine::getQueueEntryById($entityID);
            $queueRecord
              ->set('archived', TASK_ARCHIVE_REGEN);
            $queueRecord
              ->save();
          }
        }

        // And now we create the task being looped back over to:
        $queueID = $this
          ->createProductionTask($taskID, $template, $processID);
      }
    }

    //end foreach next task
  }
  else {

    // This is the condition where there isn't a next step listed in the task
    // this doesn't necessarily suggest an end of process though, as that's what
    // the end task is meant for.  However, we have a situation here where
    // the task doesn't specify a next task.  Perhaps this is legitimately the end
    // of a task chain while other parallel tasks continue to execute.
    // We will consider this a NOOP condition and do nothing.
  }
}