Yii 1.1: dynamic-redirection

perform a call to an action to request data and dynamically redirect when data are retrieved
1 follower

Requirements

Define your 'timezone' as a parameter inside main.php configuration file (Yii params)

Problem

Let's begin with an example: Let's say that I have designed a certain flow of actions and I am in a point where the value of the id of a Active Record model is missing. I cannot continue until I have this id !

No worries since I already have implemented a module which plays the role of the Finder. So inside this module I have implemented various forms and the user can use the name of the model, its serial number, or other attributes to find this model.

Either path I may follow I always end up with the same result: Choosing a specific model with a unique id which is what I wanted in the first place! So this Finder is going to be very useful. I am going to take advantage of it in many parts of my application!

How do I know where to redirect after I have retrieved the id of this model ?? I redirect to the one who requested this id of course. Ok how does someone model all this?!

Solution (using database)

To simplify things let's take as an assumption that we are using a single tab for our web application meaning that the session id is enough.

So, continuing the above example, let's say we are inside the action of a controller which has the need for the id of the model. So here we have the caller. The one who calls other controller actions to get what he wants.

We could set the caller as the route of the action but because the same action can receive several GET parameters the caller is the entire uri. This call is saved in a persistent storage location, in our case inside the database!

So we have a database table “action_call” and the corresponding active record model “ActionCall” in which we save the uri of the caller, the session id (this is where the default scope comes in handy) and also the timestamp of when the call has happened. You will see at the end why the timestamp is important. Who is the one who receives the call?

To make it more difficult, does the caller calls only one action? No! Because the caller might be in need of more than one data. The caller might call the Finder to retrieve the id of the model and the Searcher for the id of another model!

Therefore we have a second database table called “call_data” and the corresponding active record model “CallData”. This model belongs to ActionCall or in other words ActionCall has many CallData models. Let's define the callee as the one who receives the call. The callee is an attribute of the CallData and (as stands for the caller) is a uri.

Note here that the callee need NOT to be the same action who answers the call. The callee is only the first action of an arbitrary flow of actions which must conclude to an action that DOES answer the call!

In CallData we save in the attribute data_name the name of the data just to be sure of what we are asking for. The data we want to retrieved are also saved inside the “call_data” table in a BLOB. We exploit the serialize and unserialize functions of php to make that happen. Finally we have the “answered” boolean attribute which answers whether this particular call is answered or not.

By convention only if all calls performed by a single caller are answered the call is considered to be completed!

So we perform the call. The call redirects us to the (first) callee and then at some point the call is answered and we get redirected back to the caller which retrieves the data from the database!

In case there are more callees pending then we get redirected to the second callee and again back to the caller where now retrieves both data from the database and so on. The caller will continue to do the rest of his job only after all the calls have been answered!

A small drawback: What happens if the user presses refresh on his page after the call is answered? He has to go through all the calls to provide the necessary data once more! Simple fix: have all the answered data be available in GET and retrieve them from there.

Major drawback: You have to create a cronjob that removes all unanswered calls periodically every two or more hours according to traffic!

Usage

Create two database tables. SQL scripts for creating tables in MySQL:

CREATE  TABLE IF NOT EXISTS `action_call` (
`id` INT NOT NULL AUTO_INCREMENT ,
`caller` VARCHAR(255) NOT NULL ,
`sessionID` VARCHAR(255) NOT NULL ,
`tabID` VARCHAR(255) NULL COMMENT 'NOT USED YET' ,
`called` TIMESTAMP NOT NULL ,
PRIMARY KEY (`id`) )
ENGINE = InnoDB

CREATE  TABLE IF NOT EXISTS `dooz`.`call_data` (
`id` INT NOT NULL AUTO_INCREMENT ,
`callee` VARCHAR(255) NOT NULL ,
`data_name` VARCHAR(255) NOT NULL ,
`data` BLOB NULL ,
`answered` TINYINT(1) NOT NULL ,
`action_call_id` INT NOT NULL ,
PRIMARY KEY (`id`) ,
INDEX `fk_call_data_action_call1` (`action_call_id` ASC) ,
CONSTRAINT `fk_call_data_action_call1`
FOREIGN KEY (`action_call_id` )
REFERENCES `dooz`.`action_call` (`id` )
ON DELETE CASCADE
ON UPDATE RESTRICT)
ENGINE = InnoDB

Copy paste ActionCall.php and CallData.php inside your "models" folder.

Perform a call inside an action of a controller with a snippet of code that looks like this:

$data_name = 'name';
$answers = ActionCall::call($this,array(
    array(
        'callee' => CHtml::normalizeUrl(array(
            'profile/change_name',
            'name' => $states['name'],
        )),
        'data_name' => $data_name,
    ),
),array(
    'service' => $service,
));
$name = $answers[$data_name];

Above you see that the caller calls the action with route "profile/change_name" This actions takes a parameter 'name' which we define normally inside the array. Because we want the uri we use the CHtml::normalizeUrl method. Also the data_name will be 'name'. You see that this is an array inside another array. Use multiple arrays with the keys 'callee' and 'data_name' to perform multiple calls at once as described above ;) Also the second parameter of ActionCall::call ('service' in our example) is the GET parameters that will the caller will need when we redirect back to it!

And answer the call to the action of the controller which finally answers the call by providing the necessary data with a snippet of code that looks like that:

$array = array('name'=>$model->name);   //performing this trick for assertion
$callData = CallData::getCall(Yii::app()->request->requestUri);
if($callData instanceof CallData) {
    $data_name = $callData->data_name;
    $callData->data = $array[$data_name];   //so this must be correct now
    $uri = $callData->answerCall();
    $this->redirect($uri);
}

First we assert that data_name is what we want. We save the data inside the database and when we answer the call we get back the uri of the caller. This way we redirect back to the caller and after all calls are answered we can continue!

Do not forget to create the cronjob as described above in order to not have redundant unanswered calls inside the database

Total 1 comment

#9376 report it
pligor at 2012/08/07 11:04am
From developer:

I would love to have some feedback! Meanwhile I will try to convert this implementation to be session-based and not db-based! This way we get rid of the cronjob I ask above, the serialization of data etc.

Leave a comment

Please to leave your comment.

Create extension