Temps de lecture : 2 minutes

PHP, Oracle. Deux noms qui semblent être totalement opposés, et qui, combinés, font peur à la plupart des développeurs PHP. Non sans raisons. Configurer Doctrine DBAL avec un serveur Oracle fait parti de ces raisons. Dans cet article nous allons voir comment configurer Doctrine avec une chaine de connexion Oracle de type « EZ Connect ».

Première étape : consulter la documentation de Doctrine concernant la configuration du driver PHP Oracle (OCI8), qui a première vue semble claire.

Généralement, les informations dont nous disposons pour se connecter à la base de données sont les suivantes : un nom d’utilisateur, un mot de passe et un hôte de la forme ip:port/service.domaine (merci à ce sujet de Stackoverflow d’ailleurs).

Les informations à compléter pour connecter Doctrine à Oracle sont les suivantes : nom d’utilisateur, mot de passe, hôte, port, nom de la base de données, charset. Après un peu de lecture supplémentaire pour savoir ce qu’est un service, un SID et en quoi ils diffèrent du nom de la base de données, il semblerait que je dispose de nous disposions de toutes les informations nécessaires pour connecter Doctrine DBAL à Oracle.

Première tentative : découper la chaine de caractères hôte afin d’avoir le port et le nom de la base de données à renseigner pour Doctrine. Première tentative infructueuse puisque Doctrine n’arrive pas à se connecter au serveur et lance l’exception suivante :

ORA-12505: TNS:listener does not currently know of SID given in connect descriptor

En y regardant de plus près l’exception est levée dans le constructeur de la classe Doctrine\DBAL\Driver\OCI8\OCI8Connection lors de l’appel à oci_connect ou oci_pconnect.

Un petit tour sur la documentation de oci_connect nous indique que la fonction prend 5 paramètres : username, password, connection_string, character_set et session_mode. N’ayant pas de doutes sur les deux premiers paramètres, c’est le connection_string qui nous intéresse.
Le dump de la variable $db servant de connection_string donne la chaine de caractères suivante :

(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=127.0.0.1)(PORT=1554)))(CONNECT_DATA=(SID=dbname.world)))

Partant d’un code existant utilisant directement la fonction oci_connect avec un troisième paramètre formaté comme ceci : ip:port/service.domaine, le dump ci-dessus est surprenant puisqu’il semblerait que les deux chaines de caractères soient toutes les deux des chaines de caractères de connexion Oracle.

Après un peu de reverse engineering supplémentaire, on découvre que c’est la classe Doctrine\DBAL\Driver\OCI8\Driver qui instancie la classe Doctrine\DBAL\Driver\OCI8\OCI8Connection :

public function connect(array $params, $username = null, $password = null, array $driverOptions = array())
{
    return new OCI8Connection(
        $username,
        $password,
        $this->_constructDsn($params),
        isset($params['charset']) ? $params['charset'] : null,
        isset($params['sessionMode']) ? $params['sessionMode'] : OCI_DEFAULT,
        isset($params['persistent']) ? $params['persistent'] : false
    );
}

Ce qui nous intéresse ici est la méthode _constructDsn de cette première classe qui renvoie la connection_string :

protected function _constructDsn(array $params)
{
    $dsn = '';
    if (isset($params['host']) && $params['host'] != '') {
        $dsn .= '(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)' .
               '(HOST=' . $params['host'] . ')';

        if (isset($params['port'])) {
            $dsn .= '(PORT=' . $params['port'] . ')';
        } else {
            $dsn .= '(PORT=1521)';
        }

        if (isset($params['service']) && $params['service'] == true) {
            $dsn .= '))(CONNECT_DATA=(SERVICE_NAME=' . $params['dbname'] . '))';
        } else {
            $dsn .= '))(CONNECT_DATA=(SID=' . $params['dbname'] . '))';
        }
        if (isset($params['pooled']) && $params['pooled'] == true) {
            $dsn .= '(SERVER=POOLED)';
        }
        $dsn .= ')';
    } else {
        $dsn .= $params['dbname'];
    }

    return $dsn;
}

Si le paramètre « host » est défini la connection_string sera de la même forme que celle générée un peu plus haut. Cependant, si le paramètre « host » n’est pas défini la connection_string sera directement égale au paramètre « dbname », plus précisément une chaine EZ Connect de la forme ip:port/service.domaine.
Pour rappel, en PHP, une variable n’est pas définie quand elle n’existe pas ou que sa valeur est à « null ».

Seconde tentative donc, mais avec la paramètre « host » défini à « null » et la chaine EZ Connect affectée au paramètre « dbname » :

$app->register(new Silex\Provider\DoctrineServiceProvider(), array(
    'db.options' => array(
        'driver'   => 'oci8',
        'host' => null,
        'dbname' => HOST,
        'user' => LOGIN,
        'password' => PWD,
        'charset' => 'UTF8',
    ),
));

Cette fois-ci, Doctrine DBAL arrive à se connecter correctement à la base Oracle.

Cet article est originalement paru sur le blog d’Antoine Descamps.