Utilisation de Cron via PHP - Partie 2
Lors de la première partie, nous avons vu comment construire l'objet principal de gestion de Cron en PHP. Dans cette seconde partie, nous allons voir l'objet qui gère une tâche, qui correspond aussi à une ligne du fichier de crontab.
Reprenons notre objet CrontabJob initié dans la première partie
final class CrontabJob {
}
Pour commencer la construction de notre objet, regardons l'ensemble des variables dont il va avoir besoin. Les premières sont faciles à déterminer, puisqu'il s'agit des valeurs qui compose une ligne de Crontab. Nous avons donc les valeurs qui définissent la périodicité ainsi que la tâche à exécuter.
/**
* @var string $minute
* @brief Minute value for crontab setup.
*/
private $minute = '*';
/**
* @var string $hour
* @brief Hour value for crontab setup.
*/
private $hour = '*';
/**
* @var string $dayOfMonth
* @brief Day of month value for crontab setup.
*/
private $dayOfMonth = '*';
/**
* @var string $month
* @brief Month value for crontab setup.
*/
private $month = '*';
/**
* @var string $dayOfWeek
* @brief Day of week value for crontab setup.
*/
private $dayOfWeek = '*';
/**
* @var srting $task
* @brief Task value for crontab setup.
*/
private $task = '';
Ensuite, nous avons l'identifiant, si vous vous souvenez bien, qui permet de retrouver une tâche, cette valeur doit être public pour être accessible depuis l'objet parent.
/**
* @var string $identifier
* @brief Job identifier.
*/
public $identifier = null;
Enfin, nous ajoutons une dernière variable permettant de stocker le détails d'une ligne, peu importe le format dans lequel on le reçoit.
/**
* @var mixed $details
* @brief Source details.
*/
private $details = null;
Ensuite, il est nécessaire de retrouver tous les appels que nous avons intégré dans notre objet CrontabEditor afin d'être surs de les intégrer. Au final, nous n'en avons que trois : l'appel à l'objet, qui fait office de point d'entrée pour définir ses informations, la méthode output qui permet d'exporter le contenu de l'objet en tant que ligne crontab et enfin la méthode is_valid qui permet de vérifier que l'objet est conforme et peut être ajouter dans Cron.
Commençons par le constructeur, avec ses paramètres.
/**
* Class constructor.
* @param mixed $details
* @param string $identifier
* @return void
*/
public function __construct($details=null, $identifier=null) {
}
Son contenu va être assez simple, puisqu'il s'agit d'abord reprendre le contenu des paramètres
/* Set up values */
if (!is_null($details))
$this->details = $details;
if (!is_null($identifier))
$this->identifier = $identifier;
Ensuite nous commençons à traiter les données, à commencer par les données principales
/* Read job details */
$this->load_fromDetails();
Comme vous pouvez le voir, je décompile pas les données directement depuis le constructeur, mais avec une méthode qui sera dédiée à cette tâche. Par expérience, cela permet d'une part de réutiliser la méthode dans d'autres cas que la création d'un nouvel objet, mais aussi de rendre le code plus lisible en segmentation chaque action dans une méthode dédiée.
Enfin, si aucun identifiant n'est indiqué, nous en créons un.
/* Check identifier is set */
if (is_null($this->identifier))
$this->identifier = uniqid();
Reprenons les bonnes habitudes et regardons le constructeur au complet
/**
* Class constructor.
* @param mixed $details
* @param string $identifier
* @return void
*/
public function __construct($details=null, $identifier=null) {
/* Set up values */
if (!is_null($details))
$this->details = $details;
if (!is_null($identifier))
$this->identifier = $identifier;
/* Read job details */
$this->load_fromDetails();
/* Check identifier is set */
if (is_null($this->identifier))
$this->identifier = uniqid();
}
A ce niveau là, nous avons toujours 3 méthodes à écrire, au minimum pour remplir les conditions déjà émises : load_fromDetails, output et is_valid.
load_fromDetails est une méthode privée, puisqu'elle n'a pas besoin d'être appelée en dehors de l'objet. Elle ne prend aucun paramètre puisque les détails sont déjà contenus dans l'objet, et return le succès de la lecture.
/**
* Load job settings from details.
* @return boolean success
*/
private function load_fromDetails() {
}
Selon le type de la variable $details, nous avisons. Pour commencer, si la variable est nulle, nous ne faisons rien, mais nous retournons tout de même la lecture comme un succès.
if (is_null($this->details))
return true;
Si $details est une liste nous appliquons une sous-méthode dédié
if (is_array($this->details))
return $this->load_fromArrayDetails();
Si $details est une chaine de caractère, nous en appliquons une autre
elseif (is_string($this->details))
return $this->load_fromStringDetails();
Sinon, nous ne savons pas quoi faire la donnée telle qu'elle est
return false;
Voici la méthode au complet
/**
* Load job settings from details.
* @return boolean success
*/
private function load_fromDetails() {
if (is_null($this->details))
return true;
if (is_array($this->details))
return $this->load_fromArrayDetails();
elseif (is_string($this->details))
return $this->load_fromStringDetails();
return false;
}
Vous allez me dire, nous ne sommes pas plus avancés, puisque nous sommes maintenant à 4 méthodes à écrire. Mais notre code sera bien plus simple à relire et éventuellement à corriger comme cela. Attaquons directement les sous-méthodes de lecture.
/**
* Load job settings from details in an array.
* @return void
*/
private function load_fromArrayDetails() {
}
Basiquement, nous allons simplement regarder dans la liste si nous avons chacune de nos valeurs.
if (isset($this->details['minute']))
$this->minute = $this->details['minute'];
if (isset($this->details['hour']))
$this->hour = $this->details['hour'];
if (isset($this->details['dayOfMonth']))
$this->dayOfMonth = $this->details['dayOfMonth'];
elseif (isset($this->details['day']))
$this->dayOfMonth = $this->details['day'];
if (isset($this->details['month']))
$this->month = $this->details['month'];
if (isset($this->details['dayOfWeek']))
$this->dayOfWeek = $this->details['dayOfWeek'];
if (isset($this->details['task']))
$this->task = $this->details['task'];
if (isset($this->details['identifier']))
$this->identifier = $this->details['identifier'];
Et nous terminons avec le résultat
return true;
Ici, j'ai souhaité que la méthode fonctionne de manière soft. Si une des valeurs est absente, nous gardons sa valeur par défaut. Nous pourrions choisir de dire que si une des valeurs n'est pas définie, la méthode retourne false, la tâche n'est donc plus exportable vers Cron.
Comme vous pouvez le voir aussi, nous sommes gentils sur la valeur dayOfMonth qui accepte aussi une valeur simplement nommée day dans la liste. Toujours par expérience, ajouter un peu de souplesse et imaginer là où on risque de patauger un peu permet de rendre son code plus flexible, et plus facilement utilisable.
La méthode load_fromStringDetails permet de lire la variable $details depuis une chaine de caractère. En fait, tout simplement, elle doit être capable de lire une ligne écrite dans crontab.
/**
* Load job settings from details in a string.
* @return void
*/
private function load_fromStringDetails() {
}
Il y a plusieurs façons de lire et décomposer une chaine de caractère. Voici celle que j'ai trouvé la plus simple pour cette méthode.
D'abord, nous commençons par décompiler la chaîne en un tableau, en indiquant que les séparateurs sont les espaces.
$details = explode(' ', $this->details);
Vérifions si nous avons le nombre minimum d'éléments dans le tableau, sachant que nous avons besoin des valeurs : minutes, heures, jour, mois, jour du mois et tâche
/* Check details length */
if (count($details) < 6)
return false;
Attention, si le tableau contient 6 éléments ou plus, ce n'est pas nom plus garant d'une ligne parfaitement formattée. La tâche par exemple, pourrait contenir, rien qu'à elle même plus de 5 espaces et donc valider cette condition, sans forcément prendre en compte les valeurs de la périodicité. Cependant, avoir moins de 6 éléments est nécessairement signe d'un mauvais formattage.
Puis récupérerons les valeurs de périodicité en dépilant les éléments du tableau au fur et à mesure
/* Load date and time values */
$this->minute = array_shift($details);
$this->hour = array_shift($details);
$this->dayOfMonth = array_shift($details);
$this->month = array_shift($details);
$this->set_dayOfWeek = array_shift($details);
Avant d'attaquer la lecture de la tâche, je tiens à préciser qu'il n'y a aucun contrôle sur les données en les insérant de la sorte. Selon les utilisateurs à qui sont destinés l'utilisation d'un tel outil, il peut être judicieux d'en ajouter.
Les derniers éléments compose la tâche et éventuellement l'identifiant. Il faut donc commencer par re-composer une chaine à partir des éléments restant dans le tableau
/* Rebuild task value / identifier value */
$details = implode(' ', $details);
Puis re-décomposons la, en utilisant comme séparateur le caractère permettant de mettre des commentaires.
/* Split task and identifier */
$details = explode('#', $details);
De notre nouveau tableau, la première case est donc la tâche.
/* Set task */
$this->task = $details[0];
Et si le tableau contient un second élément, c'est donc notre identifiant.
/* Load identifier if it exists */
if (isset($details[1]))
$this->set_identifier($details[1]);
Je viens de me rendre compte que j'ai oublié cette méthode set_identifier, qui est aussi utilisée depuis notre objet principal.
Et enfin, retournons le résultat comme un succès.
/* Return success */
return true;
Voici les deux méthodes dans leur entièreté
/**
* Load job settings from details in an array.
* @return void
*/
private function load_fromArrayDetails() {
if (isset($this->details['minute']))
$this->minute = $this->details['minute'];
if (isset($this->details['hour']))
$this->hour = $this->details['hour'];
if (isset($this->details['dayOfMonth']))
$this->dayOfMonth = $this->details['dayOfMonth'];
elseif (isset($this->details['day']))
$this->dayOfMonth = $this->details['day'];
if (isset($this->details['month']))
$this->month = $this->details['month'];
if (isset($this->details['dayOfWeek']))
$this->dayOfWeek = $this->details['dayOfWeek'];
if (isset($this->details['task']))
$this->task = $this->details['task'];
if (isset($this->details['identifier']))
$this->identifier = $this->details['identifier'];
return true;
}
/**
* Load job settings from details in a string.
* @return void
*/
private function load_fromStringDetails() {
$details = explode(' ', $this->details);
/* Check details length */
if (count($details) < 6)
return false;
/* Load date and time values */
$this->minute = array_shift($details);
$this->hour = array_shift($details);
$this->dayOfMonth = array_shift($details);
$this->month = array_shift($details);
$this->set_dayOfWeek = array_shift($details);
/* Rebuild task value / identifier value */
$details = implode(' ', $details);
/* Split task and identifier */
$details = explode('#', $details);
/* Set task */
$this->task = $details[0];
/* Load identifier if it exists */
if (isset($details[1]))
$this->set_identifier($details[1]);
/* Return success */
return true;
}
Ne perdons pas de temps, et réalisons immédiatement notre méthode set_identifier, qui est vraiment très simple, et sans piège.
/**
* Define identifier value.
* @param string $identifier
* @return void
*/
public function set_identifier($identifier) {
$this->identifier = $identifier;
}
Vous pourriez me dire, pourquoi ne fait on pas des méthodes pour modifier les valeurs de périodicité et la tâche aussi ? Et bien, dans sa version complète, c'est ce qui a été fait. Cela permet plusieurs choses, d'une part de valider les différentes valeurs, ce que je n'ai malheureusement pas encore eu le temps de faire dans la librairie originale. D'autre part, cela permet construire un nouveau objet de toute pièce en lui injectant chacune des valeurs, plutôt que d'insérer un tableau ou une chaine directement compatible avec Cron.
A vrai dire, nous pouvons d'ores et déjà regarder la méthode is_valid, puisque, elle non plus, je n'ai pas eu le temps de la faire. Elle est donc extrêmement basique, elle répond simplement true.
/**
* Check job is valid.
* @return boolean success
*/
public function is_valid() {
/* Do the checks */
/* Return default value */
return true;
}
La validation de chacune des valeurs, n'est pas forcement quelque chose d'aisé. D'un côté, il n'y a rien à vérifier pour la tâche puisqu'elle peut contenir a peu prêt n'importe quoi.
Du côté des valeurs de périodicité, ils peuvent être un nombre, mais aussi un astérisque indiquant "toutes les valeurs", mais aussi une liste de valeurs, une plage de valeurs, ou encore des valeurs par étapes. Il n'y a rien d'insurmontable la dedans, mais cela prend tout de même un peu de temps pour les développer.
N'hésitez à proposer vos versions dans les commentaires.
Enfin, il ne reste plus qu'à créer la méthode qui permet d'exporter la tâche pour Cron. Rien de plus simple, il suffit de retourner une chaine contenant toutes les valeurs dans le bon ordre et séparées par des espaces. Il ne faut pas oublier d'ajouter l'identifiant préfixé par le caractère # qui le défini comme commentaire. Et enfin, il ne faut pas non plus oublié de mettre un saut de ligne, à la fin.
/**
* Output job.
* @return string
*/
public function output() {
return $this->minute.' '.$this->hour.' '.$this->dayOfMonth.' '.$this->month
.' '.$this->dayOfWeek.' '.$this->task.' #'.$this->identifier.PHP_EOL;
}
Et voila, vous disposez de tous les éléments pour gérer les tâches périodique depuis votre application PHP. Evidemment et comme nous l'avons vu durant l'article, les librairies sont largement améliorables, et peuvent être étendues avec les fonctionnalités dont vous pourriez avoir besoin.