I need to implement a bunch of background processes that do things like send emails, query 3rd pary api etc.
I have the task in place, but i dont fully understand how steps work, docs are a little thin on this, can someone explain how this works?
You can implement your entire Task with a single step if you want. The only point of breaking it into multiple steps is so the task progress bar has an idea of how far along the task is.
In your case, since your task is broken up into multiple unrelated processes, you might want to implement it with subtasks. So the top-level task only has a couple of steps based on the different high-level aspects of the task, and create separate task classes for those individual steps.
So let's say you have two main steps:
and (I’m guessing, just to illustrate how this could work), let’s say that the email recipients are being determined by that API call, so you need a little bit of cross-communication between those processes.
Here’s the gist of how you’d set those classes up:
Primary task class
class MyPlugin_PrimaryTask extends BaseTask
{
private $_emails;
public function getDescription()
{
return 'Blasting emails';
}
public function getTotalSteps()
{
return 2;
}
public function runStep($step)
{
switch ($step)
{
case 0:
{
return $this->runSubTask('MyPlugin_ApiTask', null, array(
'primaryTaskId' => $this->model->id
));
}
case 1:
{
return $this->runSubTask('MyPlugin_EmailTask', null, array(
'emails' => $this->_emails;
));
}
}
}
public function setEmails($emails)
{
$this->_emails = $emails;
}
}
API-calling task
class MyPlugin_ApiTask extends BaseTask
{
private $_parentTask;
private $_emails;
public function getDescription()
{
return 'Calling the external API';
}
public function getTotalSteps()
{
return 1;
}
public function runStep($step)
{
// Call the API and get a list of emails
// ...
// $emails = ...
// Pass the emails along to the primary task
$primaryTask = craft()->tasks->getTaskById($this->getSettings()->primaryTaskId);
$primaryTask->getTaskType()->setEmails($emails);
return true;
}
protected function defineSettings()
{
return array(
'primaryTaskId' => AttributeType::Number
);
}
}
Email-sending task
class MyPlugin_EmailTask extends BaseTask
{
public function getDescription()
{
return 'Sending the emails';
}
public function getTotalSteps()
{
return count($this->getSettings()->emails);
}
public function runStep($step)
{
$email = new EmailModel();
$email->toEmail = $this->getSettings()->emails[$step];
$email->subject = '...';
$email->body = '...';
craft()->email->sendEmail($email);
return true;
}
protected function defineSettings()
{
return array(
'emails' => AttributeType::Mixed
);
}
}
Check out the example PowerNapTask provided by Pixel & Tonic — It implements a Task (and Subtask) with multiple steps. It should be fairly straightforward to modify that sample code to run your own custom logic.
I'm following Brandon's example here but am having trouble passing the variable that is set from the first subtask to the second subtask. Here's my code:
Primary Task
<?php
namespace Craft;
class Products_TestPrimaryTask extends BaseTask
{
public $myVar;
public function getDescription()
{
return 'Running Primary Task';
}
public function getTotalSteps()
{
return 2;
}
public function runStep($step)
{
switch ($step)
{
case 0:
{
return $this->runSubTask('Products_TestSub1', null, array(
'primaryTaskId' => $this->model->id
));
}
case 1:
{
return $this->runSubTask('Products_TestSub2', null, array(
'subVar' => $this->myVar,
));
}
}
return true;
}
public function setPrimaryTaskVar($var)
{
$this->myVar = $var;
ProductsPlugin::log('Variable in Primary Task: ' . print_r($this->myVar, true));
}
}
Sub Task 1
<?php
namespace Craft;
class Products_TestSub1Task extends BaseTask
{
protected function defineSettings()
{
return [
'primaryTaskId' => AttributeType::Number
];
}
public function getDescription()
{
return 'Running Sub Task 1';
}
public function getTotalSteps()
{
return 1;
}
public function runStep($step)
{
$primaryTask = craft()->tasks->getTaskById($this->getSettings()->primaryTaskId);
$primaryTask->getTaskType()->setPrimaryTaskVar('hello');
return true;
}
}
Sub Task 2
<?php
namespace Craft;
class Products_TestSub2Task extends BaseTask
{
protected function defineSettings()
{
return [
'subVar' => AttributeType::String
];
}
public function getDescription()
{
return 'Running Sub Task 2';
}
public function getTotalSteps()
{
return 1;
}
public function runStep($step)
{
ProductsPlugin::log('Variable in Sub Task 2: ' . print_r($this->getSettings()->subVar, true));
return true;
}
}
So Sub Task 1 calls the setPrimaryTaskVar
method in the Primary Task, which sets the variable that then gets passed to Sub Task 2 in step 2 of the Primary Task. However, when I try to log that variable in Sub Task 2 it is showing as null.
Am I missing something obvious here?
WORKAROUND
After playing around with this a bit more, the only way I could persist the data between steps in the Primary Task was to save the variable in question to a SESSION var. So the Primary Task class has been amended to the following. Hope this helps someone else:
<?php
namespace Craft;
class Products_TestPrimaryTask extends BaseTask
{
/**
* Returns the default description for this task.
*
* @return string
*/
public function getDescription()
{
return 'Running Primary Task';
}
/**
* Gets the total number of steps for this task.
*
* @return int
*/
public function getTotalSteps()
{
return 2;
}
/**
* Runs a task step.
*
* @param int $step
* @return bool
*/
public function runStep($step)
{
switch ($step)
{
case 0:
{
return $this->runSubTask('Products_TestSub1', null, array(
'primaryTaskId' => $this->model->id
));
}
case 1:
{
return $this->runSubTask('Products_TestSub2', null, array(
'subVar' => $_SESSION['subVar']
));
}
}
return true;
}
public function setPrimaryTaskVar($var)
{
$_SESSION['subVar'] = $var;
ProductsPlugin::log('Variable in Primary Task: ' . print_r($_SESSION['subVar'], true));
}
}