Retour d'expérience en php

Cette page décrit les outils et procédures que je me suis forgés dans la cadre de mon apprentissage en solitaire du développement de site à l'aide du langage PHP.

Les plus gros problèmes que j'ai rencontrés on été

Plan

Précautions initiales

Il faut utiliser à fond la session ($_SESSION) dans laquelle on peut tout stocker SAUF les ressources. Sont des ressources toutes les références hors du monde php : handlers de fichiers, connexion à une base de données …)

Un sous répertoire commun permet de regrouper les images, css, script à inclure ...

Mode développement

Il est indispensable de configurer le fichier /etc/php/7.2/apache2/php.ini pour travailler en mode développementt et ainsi avoir les erreurs affichée dans la page, même si elle ne sont pas fortement explicite. Sinon, l'erreur provoque une page blanche, encore moins explicite. Je l'ai modifié :
error_reporting = E_ALL 
display_errors = On		

Organisation

Un script en ligne de commande

Pour tester, valider des expressions, fonctions plus ou moins tordues, une page php n'est pas indispensable, un simple script exécutable en ligne de commande peut faire l'affaire

Avec en première ligne la référence à l'interpréteur (#! /usr/bin/php), et en le rendant exécutable (chmod +x test.php) on peut le lancer comme un fichier quelconque script local : ./test.php

#! /usr/bin/php

<?php
//phpinfo () ;
/* default locale is taken from this ini setting */
ini_set('intl.default_locale', 'fr_FR');
$cal = IntlCalendar::createInstance() ;
echo IntlDateFormatter::formatObject($cal) , "\n\r";

Un fichier d’entête

Ce fichier sera inclus dans chaque fichier php de l'application. Il permet de
<?php
/*
  Créé le : 14 décembre 2019, 11:21:00
  Auteur     :  
*/
session_start();
/*
	Toutes les variables stockées dans la session deviennent des variables du script
*/
foreach($_SESSION as $key=>$valeur)
	$$key = $valeur ;
/*
*	Séparer la connexion de ce fichier permet de 
*		ne changer que le fichier connexion.php pour passer
*		d'un hébergement à l'autre (site de développement vers site de production)
*/
require 'connexion.php' ;
/*
*	Quelques fonctions fort utiles dans bien des cas dont
*		la fonction trace($message, $script) utilisée ci-dessous
*		les fonctions de formatage de date en fonction de l'init_set ci-dessous
*		et bien d'autres
*/
require 'fonctions.inc.php' ;
/*
*		un pied de page à insérer en bas de chaque page
*/
$piedPage = "<p align='center'>© 2019-" . date("Y") . "</p>";
/*
* default locale is taken from this ini setting */
*/
ini_set('intl.default_locale', 'fr_FR');

//$trace = true ; // Active une trace 
$trace = false ; // Pas de trace
$_SESSION['trace'] = $trace ; //pour ici ou ailleurs
if ($trace)
 	{switch (session_status ())
 		{
 			case PHP_SESSION_DISABLED : 
 				$message = "Session désactivée";
 				break ;
 			case PHP_SESSION_NONE :
 				$message = "Sessions activée mais inactive";
 				break ;
 			case PHP_SESSION_ACTIVE :
 				$message = "Session activée et active";
 				break ;
 		} ;
 		recordLog ("trace", "{$message} dans enTete.php") ;
 	} ;
?>

Une page espionne

Une simple page php, qui inclut le fichier enTete.php va permettre d'afficher tout le contenu de la session, des bribes de cette session ... ou suppléer le script php quand des données de la session doivent être testées.

<?php
require 'commun/enTete.php' ;
if (isset ($_POST['effacer']))
{
    session_unset();
    $_SESSION['loginTime'] = time();
    $loginTime = $_SESSION['loginTime'] ;
}
if (isset($_POST['preTest']))
{
    header("Location: preTest.html");
}
if (isset($_POST['razLog']))
{
    $requete = "call consomacfgspip.viderLog();" ;
    $connexion->query($requete) ;
}
?>
<html>
    <head>
      <link href="commun/consom.css" media="all" rel="stylesheet" type="text/css" />
  </head>
  <body>
  <h1>Test du <?=strftime('%A %d %B %Y à %H:%M:%S', time())?></h1>
<?php

/*
* On peut ici inspecter les variables de la session, tester des accès aux bases ...
*/
ecrireTableau($_SESSION, "La session") ;
if (isset($_POST['log'])
        && $_POST['log'] == "on"
    )
    {
        echo "<h2>Les logs</h2>" ;
        $tableLog = tableSQL("select * from log order by `id` desc ;");
        if($tableLog == null || count ($tableLog) == 0)
            echo "<p>Y'en a pas !!!</p>" ;
        else 
        {
            echo '<table><th align="center">timeStamp</th><th headers="niveau"',
             'align="center">niveau</th>',
                '<th headers="executant" align="center">Exécutant</th><th headers="message"',  
                'align="center">message</th>' ;
            foreach($tableLog as $t)
                echo "<tr><td>", $t['timeStamp'], "</td><td>", $t['niveau'], 
                  "</td><td>", $t['executant'], "</td><td>", $t['message'], "</td></tr>" ;
            echo "</table>" ;    
        }        
    }
?>
<form action="test.php" method="post">
    <label for="log">Afficher les log</label>
    <input type="checkbox" id="log" name="log" value"afficher les logs" checked="on" />
    <input type=submit name ="effacer" value="réinitialiser la session" />
    <input type=submit name="preTest" value="retour au pré-test"/>
    <input type=submit name="razLog" value="Effacer les log"/>
</form>
</h1>
<?= $piedPage ?>
</body>
</html>

Quelques fonctions

Quelques fonctions qui me sont fort utiles.

La fonction recordLog

La fonction recordLog () peut être utilisée dans tout code php ayant inclus les fichiers enTete.php ou au moins connexion.php pour l'accès à la base. Ces résultats peuvent être observés avec un outil pour mySql comme WorkBench MySql mais aussi avec la page espion décrite précédemment.
function recordLog ($niveau, $message)
{
	global $connexion ;
	
	$executant = $_SERVER['SCRIPT_NAME'] ;
	$requete = "INSERT INTO `log` (`timeStamp`, `niveau`, `executant`, `message`) "
		."VALUES (now(), \"{$niveau}\", \"{$executant}\", \"{$message}\") ;" ;
	return $connexion->query($requete) ;
}
Une simple fenêtre popup à utiliser dans un bloc php situé entre les balises <body> et </body> ;

Note : la gestion des \ n'est pas évidente, il faut vérifier pour chaque message.

function popup ($message)
{
	$filtre = '"' . "'" ;
	echo '<script type="text/javascript">alert (\'', addcslashes($message, $filtre) , '\');</script>' ;
} 
ou mieux utiliser une boite de dialogue avec la balise <dialog> qui ne fonctionne pas, sans configuration, avec certains butineurs;

Note : les problèmes des \n ne sont pas complètement résolus quand on doit se rabattre sur la fonction popup ().

function popDialog ($nomDialogue, $message)
{
	$refusDialog = addslashes($message)
		. "\\nVotre explorateur web n\'accepte pas les dialogues améliorés\\n"
		.	"pour y remédier voir la page estay.fr/Dialog.html" ;
	echo "<script type=\"text/javascript\">", 
		"dialogue = document.getElementById('", $nomDialogue, "');",
		"if (typeof dialogue.showModal === \"function\") ", "dialogue.showModal () ;",
		 "else alert (\"", $refusDialog, "\") ;" , "</script>";
}  

La fonction ecrireTableau

Elle remplace avantageusement, avec une meilleure présentation, les fonctions print_r () et var_dump (). Elle est récursive et présente agréablement les variables portant des noms contenant les mots date ou time.
function ecrireTableau($tableau, $nom)
    {
        echo "-------------------{$nom}---------------------$lt;br />";
        if (!isset($tableau) || count($tableau) == 0)
        {
            echo "Vide<br />----------------------------------------------------<br />";
            return;
        }
        echo "<table>";
        foreach ($tableau as $key => $value)
        {
            echo "<tr><td align='right'>{$key }</td><td>";
            if (stripos($key, 'time'))
                echo "le ", date("j/m/y H:m:s T", $value);
            else if (stripos($key, "date"))
            	echo "le ", stamp($value) ;
            else
            	if (is_array($value)) ecrireTableau($value, $key)	 ;
               else echo $value;
            echo "</td></tr>";
        }
        echo "<</table>----------------------------------------------------<br />";
    }

Les fonctions d'accès aux bases

Il peut être pratique de stocker les résultats des requêtes sql dans la session sous forme de tableaux associatifs représentant un tuple ou tous les tuples d'un curseur.

Pour garantir la fermeture d'un curseur, les fonctions suivantes offrent facilitation et uniformisation. Ces fonctions ne vérifient aucunement la rectitude des requêtes.

La première tupleSQL () retourne le premier tuple de la requête, a priori unique, alors que la suivante tableSQL () produit une table des tuples produits par la requête

function tupleSQL (string $requete)
{
	global $connexion ;
	
	$reponse = $connexion->query($requete) ;
	$tuple = $reponse->fetch(PDO::FETCH_ASSOC) ;
	$reponse->closeCursor () ;
	return $tuple ;
}

function tableSQL (string $requete)
{
	global $connexion ;
	
	$reponse = $connexion->query($requete) ;
	$tuple= array () ;
	while ($t = $reponse->fetch(PDO::FETCH_ASSOC))
	{
		$tuple[] = $t ;
	}
		$reponse->closeCursor () ;
	return $tuple ;
}
Creative commons