You are viewing revision #10 of this wiki article.
This version may not be up to date with the latest version.
You may want to view the differences to the latest version.
Introduction ¶
Web applications normally execute almost immediately the user's requests. But some requests, like statistical reports, take long time to execute.
This article discusses how one can run a long task in background in yiiframework 1.1 using Ajax technique.
Approaches ¶
There are different ways to run asynchronously background jobs in yiiframework. Let's see some of them.
Console application ¶
The console application is proper to run periodically tasks using operating system schedulers such as cron
. One can, for example, send emails from email queue every 15 minutes or collect statistical information every night.
The console applications can access the same models of the web application reusing most of code. The console applications are appropriate for generic system jobs, but are not suitable for tasks for single users. In fact, there is no web user in console application and hence no role authorizations and row level access limitations normally applied to web users.
BackJob extension ¶
The BackJob extension of Siquo can start actions in background and monitor their execution. It can initiate a controller's action of the Yii web application as current or as an anonymous user. Running an action as current user gives the possibility to execute the code applying usual role authorizations and row level access limitations.
Unfortunately in this approach the yiiframework considers the user's session cookies tampered if the web application uses the cookie validation. In this case the web application is executed with anonymous identity.
Ajax ¶
The Ajax technique discussed in this article gives the possibility to start a controller's action as current user without leaving the web page. This can be used to run long tasks with authorizations given to the user reusing the same Yii controllers and models. One should only intercept the result of the finished task.
Example with ajaxSubmitButton ¶
This is a fictional example of a statistical reporting. The user specifies a parameter in a web form, then invokes the long task. The task produces a file to be downloaded.
The model Example
contains a method doTask()
executing a long task. This method takes a parameter and yields a blob result. The action runTask
in the controller ExampleController
renders the web form task
and invokes the Example.doTask()
method when requested. The form runTask
contains one text field, a submit button and an ajaxSubmitButton.
The workflow is as follows. The user calls the runTask
form, specifies the parameter's value and presses one of the submit buttons. Each button is designed to call the ExampleController.actionRunTask()
action passing the text parameter. The controller in turn calls the Example.doTask()
method with the specified parameter. The result of the Example.doTask()
method is rendered to the user as file to be downloaded.
When the user presses the usual submit button, he/she should wait for the result. The ajaxSubmitButton instead runs the action in background without leaving the web form; the user is informed that the task is started and that he/she can download the result later returning in the same form.
The result of the background task should be kept in a database table longtask
in a blob column. This table keeps one record for each user/task. This example uses also the relative Longtask
model and the LongtaskController
controller on order to save and retrieve the task's results.
To run this example, execute the action example/runTask
.
Table longtask ¶
This table keeps the results of the tasks. Follows the sql script for this table for the MySql database:
[sql]
CREATE TABLE longtask (
id int(11) NOT NULL AUTO_INCREMENT,
end_time datetime DEFAULT NULL,
task text,
username text,
filename text,
mime text,
cont longblob,
PRIMARY KEY (id)
);
Longtask model ¶
This model corresponds to the longtask
table. Follow essential parts of the model:
class Longtask extends CActiveRecord {
// Usual staff...
// Initialize attributes
public function init() {
if ($this->scenario <> 'search') {
$this->end_time = date('Y-m-d H:i:s');
$this->task = Yii::app()->request->url;
$this->username = Yii::app()->user->id;
}
}
// Delete this task of this user
public function cleanMy() {
$this->deleteAll('task = :task AND username = :username',
array(':task' => Yii::app()->request->url,
':username' => Yii::app()->user->id));
}
// Permission to view the tasks
public function defaultScope() {
$u = Yii::app()->user; // user identity
if ($u->id == 'admin') // admin sees every record
return array();
else // others can see only their own tasks
return array(
'condition' => 'username = :username',
'params' => array(':username' => $u->id));
}
}
LongtaskController controller ¶
This controller contains the download
action used to download the task's result saved in the longtask
table:
class LongtaskController extends Controller {
public function accessRules() {
return array(
array('allow', // allow all users to perform 'download' action
'actions'=>array('download'),
'users'=>array('*'),
),
// other rules
);
}
// Usual staff...
// Download the document result of a task $id
public function actionDownload($id) {
$model = $this->loadModel($id);
Yii::app()->getRequest()->sendFile(
$model->filename, $model->cont, $model->mime);
}
}
Example model ¶
The model Example
contains a method doTask()
executing the long task. This method takes a parameter and yields a blob result.
class Example extends CFormModel {
// Usual staff...
// Long task. Takes parameter $par, yields blob result
public function doTask($par) {
sleep (10); // does some long work
$cont = "This is the result for $par.";
return $cont; // returns the result
}
}
ExampleController controller ¶
The action runTask
in this controller renders the web form task
and invokes the Example.doTask()
method when requested.
class ExampleController extends Controller {
// Usual staff...
// The action runTask
public function actionRunTask() {
$model = new Example; // the model to generate report
$par = ''; // form parameter
// Returned from the form
if (isset($_POST['par'])) {
$par = $_POST['par']; // parameter specified
$fn = "rep_$par.txt"; // file name
$mime = 'text/plain'; // file type
$cont = $model->doTask($par); // generate report
// Ajax button
if (Yii::app()->request->isAjaxRequest) {
$lt = new Longtask;
$lt->cleanMy(); // deletes the previous task
$lt->filename = $fn;
$lt->cont = $cont;
$lt->mime = $mime;
$lt->save(); // creates new task record
Yii::app()->end(); // end of Ajax initiated process
}
// Normal submit button: download file
else
Yii::app()->getRequest()->sendFile ($fn, $cont, $mime);
}
// Calls the form
$lt = Longtask::model()->findByAttributes(array(
'task' => Yii::app()->request->url,
'username' => Yii::app()->user->id)); // last task
$this->render('runTask', array('par' => $par, 'lt' => $lt));
}
}
runTask form ¶
This form contains one text field, a submit button and an ajaxSubmitButton. The same form contains also the link to download the result of the executed background task:
<?php
/* @var $this ExampleController */
/* @var $par string Parameter to be specified in the form */
/* @var $lt Longtask The last task */
?>
<h1>Example of a background task</h1>
<div class="form">
<?php $form = $this->beginWidget('CActiveForm', array(
'id' => 'runTask-form',
'enableAjaxValidation' => false,
)); ?>
<div class="row">
<?php echo CHtml::label('Report parameter', 'par'); ?>
<?php echo CHtml::textField('par', $par); ?>
</div>
<div class="row buttons">
<?php echo CHtml::submitButton('Submit'); ?>
<br>
<?php
echo CHtml::ajaxSubmitButton(
'Submit in background',
$this->createUrl("runTask"),
array('type' => 'POST', 'dataType' => 'json'));
?>
</div>
<?php $this->endWidget(); ?>
</div><!-- form -->
<?php
// Link to download the result
if ($lt && $lt->cont) {
echo "Result of " . $lt->end_time . ": ";
echo CHtml::link(CHtml::encode($lt->filename),
array('longtask/download', 'id' => $lt->id));
}
?>
Variations ¶
One can use this example to run in background different tasks for different users.
The application administrator should have access to the
longtask
table in order to monitor all the background jobs.One can use also ajaxLink in case there are no input parameters requested from the user.
One can keep more records per task/user to preserve the history of tasks. In this case it is better for the user to have access to the
longtask
table via CGridView.More information on the tasks can be specified in the
longtask
table. For example, it is possible to save the start time and the percentage of the execution. See for example the BackJob extension.If the task yields more then one file, they can be saved in a child table of
longtask
.The
runTask
form can give more information on the Ajax execution, such as errors detected. See the blacksheep's example.When the task's result is downloaded, it is reasonable to automatically delete the task's record from the
longtask
table. When the user has downloaded/deleted from therunTask
form, a javascript should immediately hide the download link.When the task takes very long time to execute, it is a good idea to add in the beginning of the task code the
set_time_limit()
PHP instruction.
If you have any questions, please ask in the forum instead.
Signup or Login in order to comment.