La tentation serait de créer un système qui obligerait ce genre de commandes:

$ php batch/bootstrap.php monscript

Pour executer, par exemple, le script batch/monscript.php. Cela simplifierait grandement les choses, mais c'est moche, et puis on peut faire autrement, donc on ne va pas se priver ! Nous utiliserons a cette fin la fonction magique register_shutdown_function, qui s'occupera d'appeler une fonction qui executera les scripts ad hoc.

Avant d'aller plus loin, l'arborescence de fichiers que nous allons utiliser:

batch/
	monscript.php
	runtime.php
library/
	Zend.php
	My/
		Batch.php

runtime.php sera le fichier à inclure dans chaque script, tandis que My/Batch.php contiendra la classe d'abstraction My_Batch, qui définira les comportements par défaut de nos scripts.

Voici donc la première partie de notre bootstrap, batch/runtime.php:

ini_set('include_path', get_include_path().PATH_SEPARATOR.realpath(dirname(__FILE__).'/../library/'));

require_once 'Zend.php';
require_once 'My/Batch.php';

register_shutdown_function(array('My_Batch', 'runAll'));

Bien sur, cette méthode nous oblige à définir un format à respecter pour les fonctions à executer par la suite, nous partirons donc sur les bases suivantes:

  • Chaque batch doit être constitué d'une classe dont le nom commence par My_Batch_
  • Chaque batch doit posséder une méthode executable run

Ce qui nous donne donc:

abstract class My_Batch {
	static public function runAll() {
		$classes = get_declared_classes();
		foreach($classes as $className) {
			if (preg_match('/^My_Batch_(.+)$/', $className, $matches) && method_exists($className, 'run')) {
				try {
					$batch = new $className;
					$batch->run();
				} catch (Exception $e) {
					die(sprintf("%s: %s\n", $matches[1], $e->getMessage()));
				}
			}
		}
	}
	
	abstract public function run();
}

On en profite pour catcher toute Exception indésirable pour la transformer en un die, bien plus lisible dans un terminal qu'une backtrace :-)

Maintenant que nous avons résolu les points 1 et 2, attaquons nous à la suite. Le problème principal que j'ai rencontré lors de l'établissement d'un environnement d'execution, c'est que la gestion de la configuration repose en grande partie sur la valeur de $_SERVER['HTTP_HOST'], qui n'est bien évidemment pas disponible en CLI :-) Il me faut donc forcer le passage en argument du nom de la configuration à utiliser. C'est très simple à faire, grace a la méthode que nous avons employé: il suffit de vérifier la présence de cet argument dans le constructeur de My_Batch :-)

public function __construct() {
	foreach($GLOBALS['argv'] as $i => $arg) {
		switch ($arg) {
			case '--configname':
			case '-c':
				$this->_args['configname'] = $GLOBALS['argv'][$i+1];
			break;
		}
	}
	if (!isset($this->_args['configname'])) {
		throw new Exception('-c or --configname is mandatory');
	}
}

Voilà, le gros du travail est fait. Pour le reste, c'est la routine, mise en place d'un environnement avec Zend_Config et Zend_Db, a la suite dans le constructeur:

	$this->_config = new Zend_Config(
		Zend_Config_Ini::load(
			dirname(__FILE__).'/../../app/config.ini',
			$this->_args['configname']
		)
	);
	
	$this->_db = Zend_Db::factory(
		$this->_config->db->adapter,
		$this->_config->db->config->asArray()
	);
	
	Zend_Db_Table::setDefaultAdapter($this->_db);

Enfin, voila un exemple de script utilisant ce bootstrap:

require_once 'path/to/runtime.php';

class My_Batch_MonScript extends My_Batch {
	public function run() {
		// le code ici
	}
}

And voila !