Développer pour Oracle en PHP – Gérer un répertoire de scripts SQL

But du chapitre

Le but de ce chapitre est de se créer un répertoire de scripts SQL dans lequel tout le code SQL sera stocké. Je pré suppose que mon code devra pouvoir être exécuté sur plusieurs versions d’oracle mais que chaque version d’oracle peut avoir besoin d’exécuter un code différent. Je veux donc qu’une version 12 puisse avoir un code adapté à la version 1é et qu’une version 19 puisse avoir un code adapté à la version 19. Je ne m’interdit donc aucune nouvelle fonctionnalité Oracle, mais je dois adapter mon code.

Problématiques liées à ce chapitre

Mon application va devoir connaitre la version à laquelle je suis connecté pour aller chercher le code SQL le mieux adapté.

Afin de rester maintenable je vais devoir stocker mon code SQL au meilleurs endroit possible.

Stocker le code SQL

Je considère le code SQL comme étant purement applicatif, donc intrinsèque à mon application. Il ne peut donc pas faire partie ni de la configuration (etc) ni des bibliothèques (lib). Je choisis de stocker le code applicatif dans une nouvelle arborescence: opt, et comme je vais stocker dans cette arborescence tout ce qui est purement applicatif, je vais créer un répertoire pour le sql à savoir opt/sql sous lequel je vais décliner les versions oracle (un répertoire par version) et un répertoire default pour les scripts SQL compatibles toutes versions. Ce qui va donner l’arborescence suivante :

Arborescence opt/sql

Connaitre sa version

Pour connaitre la version dans laquelle je suis je vais enrichir ma classe d’un numéro de version et la fonction connect va renseigner cette valeur, j’en profite pour faire de même pour la release et le patchset.

class oracle {
  private $conn ;
  private $version ;
  private $release ;
  private $patch ;
  
...

  /**
  * Se connecte à une base de donnée oracle et positionne des variables de session Oracle
  *
  * @param  string $user
  * @param  string $password
  * @param  string $cnxInfo
  * @param  string $cnxMode
  * @return void
  */
  function connect (string $user,string $password,string $cnxInfo,string $cnxMode) {
    $conn=oci_pconnect($user,$password,$cnxInfo,CHARSET,$cnxMode);
    if ( !$conn ) {
      return FALSE;
    } else {
      $this->conn=$conn;
    }
    oci_set_module_name($conn,MODULE);
     
    $sql="alter session set cursor_sharing=".CURSOR_SHARING;
    $stmt=oci_parse($conn,$sql);
    oci_execute($stmt);
  
    $sql="alter session set NLS_NUMERIC_CHARACTERS='".NLS_NUMC."'";
    $stmt=oci_parse($conn,$sql);
    oci_execute($stmt);
  
    $sql="alter session set NLS_SORT='".NLS_SORT."'";
    $stmt=oci_parse($conn,$sql);
    oci_execute($stmt);
  
    $sql="alter session set NLS_DATE_FORMAT='".NLS_DATE_F."'";
    $stmt=oci_parse($conn,$sql);
    oci_execute($stmt);
  
    $sql="alter session set NLS_TIMESTAMP_FORMAT='".NLS_TSTAMP_F."'";
    $stmt=oci_parse($conn,$sql);
    oci_execute($stmt);

    $sql="with t as ( select version_full b 
              from product_component_version )
select regexp_substr(b, '[0-9]+',1,1) v
     , regexp_substr(b, '[0-9]+',1,2) r
     , regexp_substr(b, '[0-9]+',1,4) p
  from t";
     $r=$this->execSql($sql);
     $this->version=$r[0]['V'];
     $this->release=$r[0]['R'];
     $this->patch=$r[0]['P'];
  }
  

On constate au passage l’utilisation de la table product_component_version et surtout de la colonne version_full qui n’est disponible que depuis la version 18 … Cette méthode va devoir être améliorée, mais pour ce chapitre, ça suffira.

Fonction d’exécution de code SQL à partir d’un nom

Je décide que le nom doit être exact et ne doit pas contenir “.sql” par exemple si je cher la requête r.sql, le paramètre de nom de fichier que je passerai à ma requête sera “r” et non “r.sql”.

/**
  * Execute le code SQL contenu dans un fichier plutôt que passé directement
  *
  * @param  string $sqlFileName
  * @param  array $binds
  * @return array
  */
  function execSqlFromFile (string $sqlFileName, ?array $binds=array()): array|bool {
    $sqlFile=$this->findSqlFile($sqlFileName);
    if ( $sqlFile ) {
      return $this->execSql( file_get_contents($sqlFile), $binds );
    } else {
      return FALSE ;
    }
  }

Je fais appel à une fonction privée qui va chercher le fichier SQL donné. Cette fonction findSqlFile va rechercher les fichier SQL dans l’arborescence décrite plus haut. On lui indique par paramétrage (dans le fichier etc/oracle.conf.php, souvenez vous) où aller chercher cette arborescence.

/**
  * Trouve le fichier sql correspondant à la version courante de la base de
  * données.
  *
  * @param  string $name
  * @return mixed
  */
  private function findSqlFile (string $name): mixed {
    if ( ! is_dir(SQL_HOME) ) {
      die ('SQL_HOME devrait être définie');
    }

    $sqlFiles=glob( SQL_HOME . '/[0-9]*/'.$name.'.sql' ); 
    if ( is_array($sqlFiles) && count($sqlFiles) > 0 ) {
      foreach ( $sqlFiles as $sqlVersion ) {
        $dv=basename(dirname($sqlVersion));
        if( !isset($sql) && $dv <= $this->version ) {
            $sqlFile=$sqlVersion;
        } else {
            if ( $dv <= $this->version ) {
                $sqlFile=$sqlVersion ;
            } else {
                break ;
            }
        }
      }
    } 

    if (! isset($sqlFile) ){
      $sqlFile=SQL_HOME.'/default/'.$name.'.sql';
    }

    if ( is_file($sqlFile) ) {
      return $sqlFile ;
    } else {
      return FALSE ;
    }
  }
Fichier index.html pour tester tout ça
define('HOME', __DIR__ );
require_once( HOME . '/etc/conf.php') ;
require_once( LIBDIR . '/oracle.php');
require_once( LIBDIR . '/table2DOut.php');

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

$ora=new oracle ;
$ora->connect( $user, $password, $tns, OCI_DEFAULT );

$out=$ora->execSqlFromFile( 'tver' );
var_dump($out);

$out=$ora->execSqlFromFile( 'tdef' );
var_dump($out);

$out=$ora->execSqlFromFile( 'tinf' );
var_dump($out);

J’ai placé bien évidemmant plusieurs fichier tver.sql (dans default, 18, 19 et 21), tdef.sql (dans default et 21) et tinf.sql (dans default, 12 et 18). J’attend que pour chaque fichier il aille me chercher le fichier qui correspond à la plus haute version, inférieure ou égale à la version de la base de données interrogée et, à défaut, qu’il aille chercher le fichier sl par défaut.

Les fichiers font tous la même chose, ils indiquent le nom du répertoire dans lequl ils se trouvent (select ‘<nom_du_répertoire>’ version from dual)

On obtient la sortie attendue suivante (la version d’oracle que j’utilise est une version 19.15.0.1.0, donc 19):

D:\DevsGit\wp-lessons\lessons\Chapitre-0004\index.php:15:
array (size=1)
  0 => 
    array (size=1)
      'VERSION' => string '19' (length=2)
D:\DevsGit\wp-lessons\lessons\Chapitre-0004\index.php:18:
array (size=1)
  0 => 
    array (size=1)
      'TDEF' => string 'default' (length=7)
D:\DevsGit\wp-lessons\lessons\Chapitre-0004\index.php:21:
array (size=1)
  0 => 
    array (size=1)
      'INF' => string '18' (length=2)

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