Asynchronous calls in PHP / Drupal

The other day I was looking for ways to do asynchronous calls in PHP and found this great hint

Now the question was how to use this in Drupal to send email or SMS without blocking the main code.

Drupal provide a fantastic queueing capability and with that it's easy to implement this. To send an email or SMS or any such activity asynchronously do the following

  1. First create a menu that will process the items async

    $items['send-sms/%/%'] = array(
    'title' => 'Send SMS From Queue',
    'page callback' => 'mymodule_process_sms',
    //since a logged in user will not be calling this, we need to ensure security so we have our own access call back instead of TRUE
    'access callback' => 'mymodule_process_sms_access',
    'access arguments' => array(2, 3),
    'type' => MENU_CALLBACK,
    );
  2. Now implement the method to process the queue

    function mymodule_process_sms() {
    $queue = DrupalQueue::get('my_queue_name', TRUE);
    $queue->createQueue(); // There is no harm in trying to recreate existing.
    while ($item = $queue->claimItem()) {
    $id = $item->data['id'];
    //process based on the id
    ...
    //delete this item from the queue
    $queue->deleteItem($item);
    }
    }
  3. Let's add security to our call

    function mymodule_process_sms_access($key, $token) {
    //get hash salt for the site which is unique for each site
    $salt = drupal_get_hash_salt();
    //concat the random string and the salt and fetch a hashed value
    $hash = hash('sha1', $salt . $key);
    //check if the hash value and token match
    return $hash === $token;
    }
  4. Now for the async request from the above link, enhanced for ssl

    function curl_request_async($url, $params, $type = 'POST') {
    $post_params = array();
    foreach ($params as $key => &$val) {
    if (is_array($val))
    $val = implode(',', $val);
    $post_params[] = $key . '=' . urlencode($val);
    }
    $post_string = implode('&', $post_params);

    $parts = parse_url($url);

    if($parts['scheme'] == 'https') {
    $host = 'ssl://' . $parts['host'];
    $port = isset($parts['port']) ? $parts['port'] : 443;
    }
    else {
    $host = $parts['host'];
    $port = isset($parts['port']) ? $parts['port'] : 80;
    }
    $fp = fsockopen($host, $port, $errno, $errstr, 30);

    // Data goes in the path for a GET request
    if ('GET' == $type) {
    $parts['path'] .= '?' . $post_string;
    }
    $out = "$type " . $parts['path'] . " HTTP/1.1\r\n";
    $out.= "Host: " . $parts['host'] . "\r\n";
    $out.= "Content-Type: application/x-www-form-urlencoded\r\n";
    $out.= "Content-Length: " . strlen($post_string) . "\r\n";
    $out.= "Connection: Close\r\n\r\n";
    // Data goes in the request body for a POST request
    if ('POST' == $type && isset($post_string)) {
    $out.= $post_string;
    }
    fwrite($fp, $out);
    fclose($fp);
    }

  5. Finally add the items that you need to process async in to a queue

    $queue = DrupalQueue::get('my_queue_name');
    $queue->createQueue();
    $queue->createItem(array('id' => 'identifier for later processing'));
    //now call the URL for processing
    $salt = drupal_get_hash_salt();
    $key = 'random string'; //generate a random string
    $hash = hash('sha1', $salt . $key);
    $url = url("send-sms/$key/$hash", array('absolute' => TRUE));
    curl_request_async($url, array(), 'GET');

There you go with an async, minimal blocking call to what-ever-task you want to perform.

Post new comment