Développer pour Oracle en PHP – Premier exemple en code propre

A propos de ce chapitre

Dans le premier post de la série sur la programmation en PHP pour Oracle j’ai connecé à écrire un package de gestion des échanges avec Oracle. Volontairement le code de ce package est inutilisable en tant que tel car il est écrit “vite fait, mal fait”. Premier problème, l’utilisation de texte dans le code principal comme par exmple

    $conn=oci_pconnect($user,$password,$cnxInfo,'AL32UTF8',$cnxMode);
    if ( !$conn ) {
      return FALSE;
    } else {
      $this->conn=$conn;
    }
    oci_set_module_name($conn,'MyModule');

Ici AL32UTF8 ou MyModule sont des chaînes de caractères qui ont peut être du sens pour l’application actuelle mais qui ne permettent pas la réutilisation du package (de la classe) par la suite. Au delà de ce problème, j’ai écrit la classe seule sur un seul nniveau d’arborescence, il convient d’en prévoir plusieurs pour bien organiser son code et pouvoir développer des projets d’envergure par la suite. Ce chapitre va donc s’attaquer à ces deux problèmes

Organistaion des fichiers

Quels sont nos besoins et quel type d’arborescence utiliser ?

On a besoin de pouvoir utiliser des fichiers de configuration pour paramétrer le fonctionnement de notre package comme le fonctionnement de notre application. Pour ce faire on utilise une arborescence proche de celle de SYSTEM V à savoir qu’on va avoir un répertoire racine sous lequel on va mettre un répertoire lib pour les bibliothèques et un répertoire etc pour les fichiers de configuration.

On aura donc un fichier index.php à la racine de notre application qui appellera le fichier de configuration de l’application ( etc/conf.php ) pouis la bibliothèque oracle ( lib/oracle.php ) qui elle même appellera son fichier de configuration ( etc/oracle/conf.php )

index.php
define('HOME', __DIR__ );
require_once( HOME . '/etc/conf.php') ;
require_once( LIBDIR . '/oracle.php');
etc/conf.php
if ( ! defined('HOME') ) {
  die('Ne pas utiliser ce fichier sans avoir configuré la constante HOME') ;
}

define ('LIBDIR', HOME . '/lib');
define ('CONFDIR', HOME . '/etc');
lib/oracle.php
if ( ! defined ('CONFDIR') ) {
  die ('Ce fichier ne peut être lancé si la contante CONFDIR n\'est pas positionnée');
}

if ( is_file(CONFDIR . '/oracle/conf.php')) {
  require_once( CONFDIR . '/oracle/conf.php');
} else {
  die('Fichier de configuration requis: '. CONFDIR . '/oracle/conf.php');
}

class oracle {
...
}

On crée donc des constantes pour l’emplacement normalisé des fichiers, ainsi l’appel à son fichier de conf par une bibliothèque pourra systématiquement être require_once(CONFDIR . ‘/’ . $libName . ‘/conf.php’ );

Suppression des chaînes en dur dans le code

On utilise le fichier de configuration de la biblothèque ( etc/oracle/conf.php ) pour positionner des constantes et remplacer les chaines écrites en dur dans l’ancien code.

etc/oracle/conf.php
define('CHARSET','AL32UTF8');
define('MODULE','Mon Module');
define('CURSOR_SHARING','FORCE');
define('NLS_NUMC','.,');
define('NLS_SORT','binary');
define('NLS_DATE_F','YYYY-MM-DD"T"HH24:MI:SS');
define('NLS_TSTAMP_F','YYYY-MM-DD"T"HH24:MI:SS');

On modifie donc la fonction connect pour l’utilisation de ces valeurs de la mnière suivante (et pour l’extrait de code vu en introduction de ce chapitre):

    $conn=oci_pconnect($user,$password,$cnxInfo,CHARSET,$cnxMode);
    if ( !$conn ) {
      return FALSE;
    } else {
      $this->conn=$conn;
    }
    oci_set_module_name($conn,MODULE);

Et ainsi de suite pour le reste de la fonction

Ecriture de la fonction d’exécution de requête

La fonction de requêtage va prendre en argument obligatoire le texte SQL qu’on voudra exécuter et optionnellement les variables inhérentes à ce code SQL et son format de sortie (ce dernier paramètre pour utilisation ultérieure)

/**
  * Exécute le code SQL passé en paramètre en utilisant d'éventuelles variables 
  * pour une sortie au format désiré  
  *  
  * @param  string $sqlText
  * @param  array $binds
  * @param  string $format
  * @return array 
  */
  function execSql(string $sqlText, ?array $binds=array(), ?string $format='array') :array|bool {
    $ret=array();
    $stid = oci_parse($this->conn,$sqlText);

    foreach ($binds as $var => $val) {
      oci_bind_by_name($stid, $var, $val);
    }
    $r=oci_execute($stid);
    if ( !$r ) { die("Impossible d'exécuter $SQL"); }

    while ($cRow = oci_fetch_array( $stid, OCI_ASSOC+OCI_RETURN_NULLS )) {
      $ret[]=$cRow;
    }

    return $ret;
  }

Testons tout cela en utilisant le fichier index.php

index.php
define('HOME', __DIR__ );
require_once( HOME . '/etc/conf.php') ;
require_once( LIBDIR . '/oracle.php');

$user='myUser';
$password='myPassword';
$tns='myDBAlias';

$ora=new oracle ;
$ora->connect( $user, $password, $tns, OCI_DEFAULT );
$out=$ora->execSql( "select 'Hello World !!!' rt from dual" );

var_dump($out);

$out=$ora->execSql( "select 'Hello '||:who||' !!!' rt from dual", array( 'who' => 'You') );

var_dump($out);

On obtient la sortie attendue suivante :

D:\DevsGit\wp-lessons\lessons\Chapitre-0002\index.php:14:
array (size=1)
  0 => 
    array (size=1)
      'RT' => string 'Hello World !!!' (length=15)
D:\DevsGit\wp-lessons\lessons\Chapitre-0002\index.php:18:
array (size=1)
  0 => 
    array (size=1)
      'RT' => string 'Hello You !!!' (length=13)

Le code de la leçon est disponible sur notre gitlab (Chapitre-0002). il doit évidemment être adapté pour pouvoir se connecter à votre base.