File-localized global (configuration) variables in PHP

I recently developed some functions that are meant to manage global variables within a PHP script.

For example, let us assume you want your script to save some configuration variables from an execution to another.
You may want to do something like:
<?php
...
$config = get_my_script_saved_config_vars();
...
if (!empty($_POST['myVar']))
{
$config['myVar'] = $_POST['myVar'];
save_my_script_config_vars($config);
}
...
if (!empty($config['myVar']))
{
do_some_actions_using_config_var($config['myVar']);
}
...
?>

Often, when dealing with this sort global/persistent configuration variables, you have to use a database or a special file.

Now, simply include this code to your script:
<?php
/**
* Provides a simple way to manage local persistent variables.
*
* @author Enisseo <http://www.enisseo.net>
*/
class LocalConfig
{
/**
* Returns the current local configuration.
* @return mixed
*/
public static function load()
{
$file = LocalConfig::_findFile();
$config = array();
if (!empty($file))
{
$content = file_get_contents($file);
$result = array();
if (preg_match('#<\?php\s+/\*CONFIG:(.*?)ENDCONFIG\*/ \?>#i', $content, $result))
{
$configStr = trim($result[1]);
$config = @unserialize($configStr);
}
}
return $config;
}
/**
* Save the current local configuration.
* @param $config mixed
*/
public static function save($config)
{
$file = LocalConfig::_findFile();
if (!empty($file))
{
$content = file_get_contents($file);
$configStr = '<?php /*CONFIG: ' . @serialize($config) . ' ENDCONFIG*/ ?>';
$content = preg_replace('#(<\?php\s+/\*CONFIG:(.*?)ENDCONFIG\*/\s+\?>|^)#i', $configStr, $content);
file_put_contents($file, $content);
}
}
/**
* Returns the caller file.
* @return string
*/
private static function _findFile()
{
$trace = debug_backtrace();
for ($i = 0; $i < count($trace); $i++)
{
if ($trace[$i]['file'] != __FILE__)
{
return $trace[$i]['file'];
}
}
return null;
}
}

You can either copy and paste into your script or include a file containing this script. Then, you only have to use the two static functions:
<?php
include_once('LocalConfig.php');
$config = LocalConfig::load();
if (!empty($config['name']))
{
print_form_asking_for_name();
}
if (!empty($_POST['name']))
{
$config['name'] = $_POST['name'];
LocalConfig::save($config);
}

This way you do not have to worry about databases or config files in order to store and manage persistent variables: the variables are stored within the file, in the begining of the source code.

If you find any bug, please let me know.

Utiliser switch() pour gérer des processus

Voici une façon d’utiliser l’instruction switch présente sur de nombreux langages, notamment en PHP :

switch (false) {
   case doStep1(): print("Error at step 1"); break;
   case doStep2(): print("Error at step 2"); break;
   case doStep3(): print("Error at step 3"); break;
   default: print("Success!"); break;
}

Cette méthode permet notamment de gérer les retours en cas d’erreur lors du déroulement du processus. Les étapes vont être exécutées une à une et si l’une d’elles échoue (return false par exemple), le traitement associé à cette étape est exécuté puis le processus est terminé.
Si doStep1() retourne true, la comparaison avec le false du switch échoue et le programme tente avec le prochain case défini. Par contre, si doStep1() retourne false, la comparaison correspond (false == false) et le traitement associé au case est donc exécuté – en l’occurrence le traitement de l’erreur.

En somme, le fonctionnement n’est pas différent de :

if (doStep1()) {
   if (doStep2()) {
      if (!doStep3()) {
         print("Success!");
      } else {
         print("Error at step 3");
      }
   } else {
      print("Error at step 2");
   }
} else {
   print("Error at step 1");
}

ou encore de :

if (!doStep1()) {
   print("Error at step 1");
} else if (!doStep2()) {
   print("Error at step 2");
} else if (!doStep3()) {
   print("Error at step 3");
} else {
   print("Success!");
}

L’avantage de la notation en switch réside principalement dans sa lisibilité. En effet, chacune des notations précédente a au moins un défaut. Pour la première, il sont évidents : la cascade de conditions (indentation) et la séparation symétrique entre la condition et le traitement d’erreur (le traitement de l’étape 1 est à la fin, le message de succès est au milieu) entraînent une très mauvaise lisibilité du processus. Pour la deuxième, le processus reste lisible mais les conditions peuvent l’être moins puisqu’elles testent la non-validité de l’étape. Ainsi la compréhension des étapes est polluée par la négation de la condition : par le signe ! en préfixe dans l’exemple, mais éventuellement par des conditions plus tordues (!a || !b || !c…).
La structure en switch, pour peu qu’on la connaisse, offre une lisibilité adaptée au déroulement d’un processus : chaque étape se situe sur la même colonne, chaque traitement d’erreur ou de succès est proche de sa cause et les étapes sont lisibles. En faisant abstraction du langage en lui-même, la structure correspond à :

PROCESS:
   STEP1: ERROR1
   STEP2: ERROR2
   STEP3: ERROR3
   END: SUCCESS

Cette structure favorise également la décomposition de chaque étape en une fonction claire. Ainsi, au lieu d’avoir une accumulation de traitements mélangés comme c’est le cas lors de l’utilisation d’un if, la structure incite (en tout cas encourage) à nommer clairement chaque étape et à réaliser la fonction associée :

switch (false) {
   case enterLogin(): ... break;
   case enterPassword(): ... break;
   case validateForm(): ... break;
   case checkAuthentication(): ... break;
   ...
}

Lorsque quelqu’un relit le code, il peut très simplement comprendre le processus en lisant le nom de chaque fonction.

Enfin, le switch offre un léger avantage sur le if : l’instruction break. Cette dernière permet d’interrompre le processus à tout moment et peut éviter des constructions complexes. On peut par exemple écrire :

switch (false) {
   case doStep1():
      if (verbose == 0) break;
      print('Error');
      if (verbose == 1) break;
      print(error_msg());
      break;
   ...
   default:
      if (...) {
         break;
      }
      ...
}

Ce fonctionnement est moins naturel à réaliser avec un if.

Pour terminer, voici un exemple en syntaxe PHP d’une structure switch (false) qui mélange vérification et processus :

switch (false) {
   // checking if the process can be executed
   case doesTheUserWantToExecuteThisProcess():
      break;

   // checking some general conditions
   case isTheUserAuthenticated():
      redirectToLoginPage(); break;

   // checking variables
   case isPosted('title'):
      printError('The title is mandatory.'); break;
   case isPosted('text'):
      printError('The text is mandatory.'); break;
   case isPosted('category'):
      printError('The category is mandatory.'); break;

   // executing the process
   case connectToDatabase():
      printError('Database connection failed.'); break;
   case $article = Article::create(posted('title'), posted('text')):
      printError('The creation of the article failed!'); break;
   case moveArticleToCategory($article, posted('category')):
      printError('The category does not exist.'); break;
   default:
      printSuccess('Your article has been added to the category!');
}

MediaDoc : gestion de documents en mode wiki (PHP)

Le projet MediaDoc est né d’un besoin : pouvoir rapidement échanger des médias entre plusieurs personnes tout en associant à ces médias des informations de type documentation. L’idée est par exemple de pouvoir publier des codes sources, des extensions, etc. tout en permettant de construire simplement la documentation de ces fichiers.

Pour télécharger le projet dans l’état actuel : MediaDoc – Preview. Il s’agit d’une pré-version, non destinée à être utilisée dans un contexte de production. Elle n’est certainement pas exempte de bugs / failles de sécurité et a été soumise à des contraintes fortes (développement en une dizaine d’heures pour un seul fichier obligatoire), ce qui rend son code actuellement très sale et difficilement maintenable en l’état.

Pour l’utiliser, décompressez le fichier dans un dossier et c’est tout (normalement). Pas de connexion à une base de données, pas de configuration spéciale.

Le projet utilise par défaut :

  • de l’URL rewriting : supprimer/renommer le fichier .htaccess à la racine pour ne pas l’utiliser ;
  • de l’archivage des modifications : supprimer/renommer le dossier archives/ à la racine pour ne pas l’utiliser.

Une documentation succincte est fournie de base, mais en explorant un peu le contenu tout devrait être clair, je suppose.