CRON est un service disponible sur les systèmes type UNIX (et Linux) permettant d’exécuter des tâches de façon périodique. Cela peut se révéler très pratique pour certaines tâches courantes d’administration système (updatedb une fois par jour), mais aussi dans le cadre du développement d’une application web (envoi de mails asynchrones).

Il est envisageable d’écrire ces tâches cron sous la forme de scripts shell ou de scripts php-cli classiques, néanmoins, en fonction de vos développements il peut être indispensable de pouvoir accéder à certains éléments du framework (filtres, validateurs, etc.) ou de votre application (modèles, code métier, etc.).

Je vais vous présenter deux techniques pour réaliser des tâches cron en PHP qui intègrent PHP Zend Framework :

Solution 1 : Tâches cron PHP exécutées avec Apache :

La première solution n’est d’après moi pas la plus adaptée pour réaliser ce type d’opérations. Elle consiste à écrire votre tâche cron dans un controlleur Zend Framework classique, puis à l’exécuter en l’appelant depuis une requête hhtp :

wget -O /var/www/MonAppli/logs/cron.tache1.log http://www.monappli.com/crontasks/tache1?key=xxxx

De cette façon l’application sera initialisée comme d’habitude (avec la stack ZF) et vous pourrez utiliser vos modèles et éléments préférés de Zend Framework.
Un cron est un traitement exécuté par le système d’exploitation UNIX ou Linux. Il est donc « dommage » de passer par Apache pour réaliser son exécution. Il y a un dommage en matière de performances mais aussi en matière de sécurité (avec cette technique et sans un minimum de précautions vos crons sont finalement exposés par votre serveur web)
Si votre application est placée sur un hébergement mutualisé vous serez parfois obligé d’utiliser cette technique. Il existe plusieurs services gratuit sur Internet pour exécuter vos taches cron. Par exemple : http://www.onlinecronjobs.com/

Solution 2 : Tâches cron PHP exécutées avec PHP CLI :

Cette solution permet de placer une tâche dans un script qui pourra être exécuté directement par le système d’exploitation sans passer par apache, MVC, etc. Vous devez en revanche être administrateur de la machine hôte pour réaliser cette solution.

Voici l’arborescence utilisée dans cet exemple :

Un dossier « crons » a été créé dans « application ». Ce dossier contient des tâches cron et un bootstrap spécifique « BootstrapCron.php ».

Le bootstrap BootstrapCron.php est inclus par chaque tâche cron est permet d’initialiser l’application Zend Framework :

<?php

// Define path to application directory
defined('APPLICATION_PATH')
    || define('APPLICATION_PATH', realpath(dirname(__FILE__) . '/..'));

// Define path to library directory
defined('LIBRARY_PATH')
    || define('LIBRARY_PATH', realpath(dirname(__FILE__) . '/../../library'));

// Define path to public directory
defined('PUBLIC_PATH')
    || define('PUBLIC_PATH', realpath(dirname(__FILE__) . '/../../public'));


// Vos fichiers ini de configuration contiennent peut être des sections "developement",
//"production", etc. Si vous voulez faire tourner les mêmes crons dans ces différents
// environnements vous devez passer en paramètre le nom de l'environnement car
// avec PHP CLI vos fichiers .htaccess seront ignorés.
if(empty ($argv[1])) exit("L'argument APPLICATION_ENV n'a pas été livré à l’exécution");
$env = (string) $argv[1];
define('APPLICATION_ENV', $env);
putenv('APPLICATION_ENV='.$env);


// Ensure library/ is on include_path
set_include_path(implode(PATH_SEPARATOR, array(
    realpath(LIBRARY_PATH),
    realpath(APPLICATION_PATH),
    realpath(APPLICATION_PATH . '/models'),
    get_include_path(),

)));


/** Zend_Application */
require_once 'Zend/Application.php';

$application = new Zend_Application(APPLICATION_ENV, APPLICATION_PATH
                                                 . '/configs/system.ini');
$application->bootstrap();

Comme vous le remarquerez on ne démarre pas la partie MVC de l’application (pas de $application->run();)
Ce bootstrap utilise le même fichier system.ini que l’application et donc le même implémentation de Zend_Application_Bootstrap_Bootstrap (application/Bootstrap.php). L’application est donc initialisée exactement comme en mode MVC.

Il ne reste plus qu’à inclure ce code personnalisé dans vos tâche cron :

#!/usr/bin/php
<?php
include 'BootstrapCron.php';

try
{
   // Le script peut mettre jusqu'à 10 minutes à s’exécuter.
   //Cette valeur doit être réglée en fonction de vos traitements
   //c'est en quelque sorte le timeout.
    ini_set('max_execution_time', 600);

    // Le script peut utiliser 32 Mo de mémoire
    ini_set('memory_limit', "32M");

    $start = microtime(TRUE);
    print " ---------- Execution du CRON : Tâche 1 \n\n";
    flush();


    // Traitements de votre cron qui utilise vos modèles,
    // Zend Framework, etc.
   
    // Exemple bidon : Ce cron s’exécute toutes les heures.
    // Il récupère les comptes utilisateurs créés depuis plus
    // d'une heure et suspend les comptes qui ne sont pas prêts.

    $users = Model_Users::fetchAllPendingAccounts(1);

    foreach ($users as $user)
    {
        if($user->getState() == Model_Users::STATE_NOT_READY)
        {
            $user->suspendAccount();
            $mail = New Zend_Mail();
            //etc..

            print "Compte suspendu : " . $user->getId() . " \n";
            flush();
        }
    }
}
catch (Exception $e)
{
    // Gestion de l'exception.
    print "Une erreur est survenue \n";
    flush();
}

Il faut rendre le script exécutable :

$ sudo chmod +x /www/var/MonAppli/application/crons/tache1.php

Vous pouvez donc maintenant, dans votre environnement de développement par exemple, exécuter votre tâche manuellement à l’aide de la commande php :

$ php /www/var/MonAppli/application/crons/tache1.php development

ou directement car nous avons indiqué au script sont interpréteur (#!/usr/bin/php) et l’avons rendu exécutable :

$ /www/var/MonAppli/application/crons/tache1.php development

L’intérêt principal de tout cela est quand même de pouvoir exécuter ce script périodiquement à l’aide du service CRON de UNIX. Pour cela éditez le fichier /etc/crontab et ajoutez :

0 */1   * * *   jacky    php /var/www/MonAppli/application/crons/tache1.php development > /var/www/MonAppli/logs/cron.tache1.log

Cette ligne exécute la tache 1 périodiquement (toutes les heures).

Gestion de l’overlap

Un autre point important dans le traitement périodique est la gestion de l’overlap ou « chevauchement ». En effet si une de vos tâche cron s’exécute toutes les 5 minutes mais qu’à un moment donné la tâche prend 7 minutes à s’exécuter (volume de données plus important à traiter par exemple), une situation d’overlap peut se présenter. Il n’est dans ce cas peut être pas souhaitable que deux instances de votre tâche s’exécutent en parallèle.
Il existe différentes solution pour palier à ce problème, je vous recommande celle-ci qui est basée sur des fichiers de lock et le PID des processus :
http://abhinavsingh.com/blog/2009/12/how-to-use-locks-in-php-cron-jobs-to-avoid-cron-overlaps/

Cette solution est certainement perfectible. Merci pour vos commentaires ou suggestions.