Tarefas de longa duração utilizando YeAPF

Uma das vantagens de YeAPF ser orientado à criação de aplicativos chamados apenas por API é a uniformização da chamada. Com isso estamos dizendo que todas as chamadas são feitas do mesmo jeito com o que o programador pode esquecer de como inventou a roda da última vez, já que vai ser sempre do mesmo jeito. Ou, um grupo de programadores pode dividir suas tarefas e alternar responsabilidades que as peças vão acabar se encaixando.

Pois bem, uma outra vantagem é o processamento de tarefas de longa duração. Na realidade é um efeito colateral positivo mais do que uma vantagem propositalmente buscada.

Entendemos por “tarefa de longa duração”, aquele processo que não se encaixa nos 30-60 segundos que o PHP tem por padrão para responder a uma requisição de um cliente (seja este humano ou não).

Pense por exemplo, no que demora enviar um e-mail personalizado a cada cliente ou interessado do seu sistema on-line. Ou a geração do arquivo de remessa de um lote de boletos para um banco. Nada disso cabe nos 30-60 segundos que você tem por padrão e -mesmo que você tem a habilidade de extorquir o PHP para obter mais tempo- também não é prudente manter o usuário ocioso por mais do que 15 segundos. Ele com certeza vai clicar no botão de gerar remessa de novo para que “o processo vá mais rápido”.

É aqui que entram as tarefas de longa duração.

Tomemos o exemplo da geração dos boletos bancários. Digamos que seu lote é de 2500 boletos e cada boleto, além de ser gerado, será enviado por e-mail para o cliente.

Um primeiro evento seria o que o cliente dispara ao escolher o escopo dos seus boletos: Por exemplo: Junho/2015, Pato Branco/PR. O programador faia uma invocação mais ou menos assim:

ycomm.invoke(
    "boletos",
    "gerarRemessa",
    { mes: "06/2015", cidade: "Pato Branco", uf: "PR" },
    function(status, error, data) {
    }
);

Por outro lado, essa invocação seria processada pelo PHP em um script muito parecido com este:

function qboletos($a) {
    global $userContext, $sysDate, $u,
           $fieldValue, $fieldName,
           $userMsg, $xq_start;

    $useColNames = true;
    $countLimit=20;
    $ret='';

    extract(xq_extractValuesFromQuery());
    $xq_start=isset($xq_start)?intval($xq_start):0;

    switch($a)
    {
      case 'gerarRemessa':
        /* instancio o gerente de tarefas do YeAPF */
        $tarefa=new YTaskManager();

        /* monto os parametros que recebi do invoke */
        $params=array(
          'mes'=>$mes,
          'cidade'=>$cidade,
          'uf'=>$uf
        );

        /* transformo em json */
        $params=json_encode($params);

        /* crio a tarefa com 340 segundos de vida máxima por interação do laço */
        $tarefa->createNewTask("boletos", "gRemessa", $params, 340);

        /* faço qualquer outra coisa que a tarefa precise antes de começar a rodar ela */
        /* ... */

        /* habilito a tarefa */
        $tarefa->enableTask();

        break;
    }

    xq_produceReturnLines($ret, $useColNames, $countLimit);
  }
}

Com isso nossa tarefa estaria criada. Repare que em lugar do evento “gerarRemessa” utilizei “gRemessa” mas apenas o fiz aos efeitos de que fique claro onde entra cada parte. Nada impede de utilizar “gerarRemessa” já que a tarefa vai entrar na funçao tboletos() e não na qboletos()

Agora vamos criar o laço da tarefa em si mesma. A recomendação é colocar no mesmo PHP que temos a qboletos() para que fique todo junto.  O laço ficaria mais ou menos assim supondo que sua tabela se chame “boletos”

  function tboletos($a)
  {
    global $sysDate, $ytasker, $xq_start;

    /* publico o contexto operacional como se fossem variáveis locais:
       xq_start, xq_target, j_params */
    extract($ytasker->getTaskContext());

    /* garanto que xq_start seja um inteiro positivo */
    $xq_start=isset($xq_start)?intval($xq_start):0;

    switch($a)
    {
      case 'gRemessa':
            $sql="select * from boletos where data>='$dataInicial' and data<='$dataFinal' offset $xq_start";
            $q=db_query($sql);
            /* enquanto o sistema me deixe rodar a tarefa e tenha dados da tabela... */
            while (($ytasker->taskCanRun()) && ($d=db_fetch_array($q))) {
              /*  ... gero o boleto
                   ... acrescento ele à remessa
                   ... envio e-mail */

              /* incremento o 'ponteiro' para a tabela */
              $xq_start++;
              /* indico à tarefa o novo ponto de começo caso caia/finalize/seja suspensa daqui até a volta do laço */
              $ytasker->advanceTo($xq_start);
            }
        break;
    }
  }

Agora a unica coisa que falta é chamar regularmente suas tarefas (todas elas). Isto é, fazer com que o script encarregado de tocar as tarefas seja executado regularmente. No seu crond local acrescente então uma entrada assim para chamar ele a cada minuto:

*/1   *    *    *    *    wget http://example.com/task.php?s=yeapf&a=tick

Pronto.

Caso esteja utilizando Windows, o lance é o mesmo utilizando o gerenciador de tarefas agendadas dele. Apenas instale wget.

Já se estiver usando o cron do próprio hospedeiro, pode utilizar o yeapf_tick.php assim:

*/1   *    *    *    *    /usr/bin/php yeapf_ticker.php