<?php
include_once $_SERVER['DOCUMENT_ROOT'] . '/include/shared-manual.inc';
$TOC = array();
$TOC_DEPRECATED = array();
$PARENTS = array();
include_once dirname(__FILE__) ."/toc/mysqlnd.plugin.inc";
$setup = array (
  'home' => 
  array (
    0 => 'index.php',
    1 => 'PHP Manual',
  ),
  'head' => 
  array (
    0 => 'UTF-8',
    1 => 'fr',
  ),
  'this' => 
  array (
    0 => 'mysqlnd.plugin.developing.php',
    1 => 'Bien commencer la compilation d\'un plugin mysqlnd',
    2 => 'Bien commencer la compilation d\'un plugin mysqlnd',
  ),
  'up' => 
  array (
    0 => 'mysqlnd.plugin.php',
    1 => 'API du plugin du driver natif MySQL',
  ),
  'prev' => 
  array (
    0 => 'mysqlnd.plugin.api.php',
    1 => 'L\'API du plugin mysqlnd',
  ),
  'next' => 
  array (
    0 => 'book.oci8.php',
    1 => 'OCI8',
  ),
  'alternatives' => 
  array (
  ),
  'source' => 
  array (
    'lang' => 'fr',
    'path' => 'reference/mysqlnd/plugin.xml',
  ),
  'history' => 
  array (
  ),
);
$setup["toc"] = $TOC;
$setup["toc_deprecated"] = $TOC_DEPRECATED;
$setup["parents"] = $PARENTS;
manual_setup($setup);

contributors($setup);

?>
<div id="mysqlnd.plugin.developing" class="section">
  <h2 class="title">Bien commencer la compilation d&#039;un plugin mysqlnd</h2>
  <p class="simpara">
   Il est important de se souvenir qu&#039;un plugin <code class="literal">mysqlnd</code>
   est lui-même une extension PHP.
  </p>
  <p class="simpara">
   Le code suivant montre la structure basique d&#039;une fonction MINIT
   utilisée dans un plugin typique <code class="literal">mysqlnd</code> :
  </p>
  <div class="example-contents">
<div class="cdata"><pre>
/* my_php_mysqlnd_plugin.c */

 static PHP_MINIT_FUNCTION(mysqlnd_plugin) {
  /* globales, entrées ini, ressources, classes */

  /* enregistrement du plugin mysqlnd */
  mysqlnd_plugin_id = mysqlnd_plugin_register();

  conn_m = mysqlnd_get_conn_methods();
  memcpy(org_conn_m, conn_m,
    sizeof(struct st_mysqlnd_conn_methods));

  conn_m-&gt;query = MYSQLND_METHOD(mysqlnd_plugin_conn, query);
  conn_m-&gt;connect = MYSQLND_METHOD(mysqlnd_plugin_conn, connect);
}
</pre></div>
  </div>

  <div class="example-contents">
<div class="cdata"><pre>
/* my_mysqlnd_plugin.c */

 enum_func_status MYSQLND_METHOD(mysqlnd_plugin_conn, query)(/* ... */) {
  /* ... */
}
enum_func_status MYSQLND_METHOD(mysqlnd_plugin_conn, connect)(/* ... */) {
  /* ... */
}
</pre></div>
  </div>

  <p class="simpara">
   <strong>Tâche d&#039;analyse : depuis C vers l&#039;espace utilisateur</strong>
  </p>
  <div class="example-contents">
<div class="cdata"><pre>
 class proxy extends mysqlnd_plugin_connection {
  public function connect($host, ...) { .. }
}
mysqlnd_plugin_set_conn_proxy(new proxy());
</pre></div>
  </div>

  <p class="simpara">
   Processus :
  </p>
  <ol type="1">
   <li class="listitem">
    <span class="simpara">
     PHP : l&#039;utilisateur enregistre une fonction de rappel pour le plugin
    </span>
   </li>
   <li class="listitem">
    <span class="simpara">
     PHP : l&#039;utilisateur appelle une méthode de l&#039;API PHP MySQL pour se connecter à MySQL
    </span>
   </li>
   <li class="listitem">
    <span class="simpara">
     C : ext/*mysql* appelle la méthode mysqlnd
    </span>
   </li>
   <li class="listitem">
    <span class="simpara">
     C : mysqlnd se termine dans ext/mysqlnd_plugin
    </span>
   </li>
   <li class="listitem">
    <p class="para">
     C : ext/mysqlnd_plugin
     <ol type="1">
      <li class="listitem">
       <span class="simpara">
        Appel de la fonction de rappel de l&#039;espace utilisateur
       </span>
      </li>
      <li class="listitem">
       <span class="simpara">
        Ou la méthode originale <code class="literal">mysqlnd</code>, si l&#039;espace
        utilisateur n&#039;a pas défini de fonction de rappel
       </span>
      </li>
     </ol>
    </p>
   </li>
  </ol>
  <p class="simpara">
   Il faut effectuer les opérations suivantes :
  </p>
  <ol type="1">
   <li class="listitem">
    <span class="simpara">
     Écrire une classe &quot;mysqlnd_plugin_connection&quot; en C
    </span>
   </li>
   <li class="listitem">
    <span class="simpara">
     Accepter et enregistrer l&#039;objet proxy via
     &quot;mysqlnd_plugin_set_conn_proxy()&quot;
    </span>
   </li>
   <li class="listitem">
    <span class="simpara">
     Appeler les méthodes de proxy de l&#039;espace utilisateur
     depuis C (optimisation - zend_interfaces.h)
    </span>
   </li>
  </ol>
  <p class="simpara">
   Les méthodes de l&#039;objet de l&#039;espace utilisateur peuvent soit être
   appelées en utilisant <code class="literal">call_user_function()</code>,
   soit il est possible d&#039;opérer à un niveau en dessous du moteur Zend et
   utiliser <code class="literal">zend_call_method()</code>.
  </p>
  <p class="simpara">
   <strong>Optimisation : appel des méthodes depuis C en utilisant
   zend_call_method</strong>
  </p>
  <p class="simpara">
   Le code suivant montre un prototype pour la fonction
   <code class="literal">zend_call_method</code>, issue de
   <var class="filename">zend_interfaces.h</var>.
  </p>
  <div class="example-contents">
<div class="cdata"><pre>
 ZEND_API zval* zend_call_method(
  zval **object_pp, zend_class_entry *obj_ce,
  zend_function **fn_proxy, char *function_name,
  int function_name_len, zval **retval_ptr_ptr,
  int param_count, zval* arg1, zval* arg2 TSRMLS_DC
);
</pre></div>
  </div>

  <p class="simpara">
   L&#039;API Zend supporte 2 arguments. Il est possible d&#039;en avoir besoin de plus, par
   exemple :
  </p>
  <div class="example-contents">
<div class="cdata"><pre>
 enum_func_status (*func_mysqlnd_conn__connect)(
  MYSQLND *conn, const char *host,
  const char * user, const char * passwd,
  unsigned int passwd_len, const char * db,
  unsigned int db_len, unsigned int port,
  const char * socket, unsigned int mysql_flags TSRMLS_DC
);
</pre></div>
  </div>

  <p class="simpara">
   Pour contourner ce problème, il faudra faire une copie
   de <code class="literal">zend_call_method()</code> et ajouter une
   fonctionnalité pour ajouter des paramètres. L&#039;on peut
   réaliser ceci en créant un jeu de macros
   <code class="literal">MY_ZEND_CALL_METHOD_WRAPPER</code>.
  </p>
  <p class="simpara">
   <strong>Appel de l&#039;espace utilisateur PHP</strong>
  </p>
  <p class="simpara">
   Le code ci-dessous montre la méthode optimisée pour effectuer un
   appel à une fonction de l&#039;espace utilisateur depuis C :
  </p>
  <div class="example-contents">
<div class="cdata"><pre>
/* my_mysqlnd_plugin.c */

MYSQLND_METHOD(my_conn_class,connect)(
  MYSQLND *conn, const char *host /* ... */ TSRMLS_DC) {
  enum_func_status ret = FAIL;
  zval * global_user_conn_proxy = fetch_userspace_proxy();
  if (global_user_conn_proxy) {
    /* appel du proxy de l&#039;espace utilisateur */
    ret = MY_ZEND_CALL_METHOD_WRAPPER(global_user_conn_proxy, host, /*...*/);
  } else {
    /* ou la méthode originale mysqlnd = ne rien faire, être transparent */
    ret = org_methods.connect(conn, host, user, passwd,
          passwd_len, db, db_len, port,
          socket, mysql_flags TSRMLS_CC);
  }
  return ret;
}
</pre></div>
  </div>

  <p class="simpara">
   <strong>Appel de l&#039;espace utilisateur: arguments simples</strong>
  </p>
  <div class="example-contents">
<div class="cdata"><pre>
/* my_mysqlnd_plugin.c */

 MYSQLND_METHOD(my_conn_class,connect)(
  /* ... */, const char *host, /* ...*/) {
  /* ... */
  if (global_user_conn_proxy) {
    /* ... */
    zval* zv_host;
    MAKE_STD_ZVAL(zv_host);
    ZVAL_STRING(zv_host, host, 1);
    MY_ZEND_CALL_METHOD_WRAPPER(global_user_conn_proxy, zv_retval, zv_host /*, ...*/);
    zval_ptr_dtor(&amp;zv_host);
    /* ... */
  }
  /* ... */
}
</pre></div>
  </div>

  <p class="simpara">
   <strong>Appel de l&#039;espace utilisateur : structures comme arguments</strong>
  </p>
  <div class="example-contents">
<div class="cdata"><pre>
/* my_mysqlnd_plugin.c */

MYSQLND_METHOD(my_conn_class, connect)(
  MYSQLND *conn, /* ...*/) {
  /* ... */
  if (global_user_conn_proxy) {
    /* ... */
    zval* zv_conn;
    ZEND_REGISTER_RESOURCE(zv_conn, (void *)conn, le_mysqlnd_plugin_conn);
    MY_ZEND_CALL_METHOD_WRAPPER(global_user_conn_proxy, zv_retval, zv_conn, zv_host /*, ...*/);
    zval_ptr_dtor(&amp;zv_conn);
    /* ... */
  }
  /* ... */
}
</pre></div>
  </div>

  <p class="simpara">
   Le premier argument de toutes les méthodes <code class="literal">mysqlnd</code>
   est un objet C. Par exemple, le premier argument de la méthode
   connect() est un pointeur vers <code class="literal">MYSQLND</code>.
   La structure MYSQLND représente un objet de connexion
   <code class="literal">mysqlnd</code>.
  </p>
  <p class="simpara">
   Le pointeur de l&#039;objet de connexion <code class="literal">mysqlnd</code>
   peut être comparé à un pointeur de fichier standard I/O.
   Tout comme un pointeur de fichier standard I/O, un objet de
   connexion <code class="literal">mysqlnd</code> doit être lié à l&#039;espace
   utilisateur en utilisant une variable PHP de type ressource.
  </p>
  <p class="simpara">
   <strong>Depuis C vers l&#039;espace utilisateur, puis, retour</strong>
  </p>
  <div class="example-contents">
<div class="cdata"><pre>
 class proxy extends mysqlnd_plugin_connection {
  public function connect($conn, $host, ...) {
    /* &quot;pre&quot; hook */
    printf(&quot;Connexion à l&#039;hôte = &#039;%s&#039;\n&quot;, $host);
    debug_print_backtrace();
    return parent::connect($conn);
  }

  public function query($conn, $query) {
    /* &quot;post&quot; hook */
    $ret = parent::query($conn, $query);
    printf(&quot;Requête = &#039;%s&#039;\n&quot;, $query);
    return $ret;
  }
}
mysqlnd_plugin_set_conn_proxy(new proxy());
</pre></div>
  </div>

  <p class="simpara">
   Les utilisateurs PHP doivent pouvoir appeler l&#039;implémentation
   du parent d&#039;une méthode écrasée.
  </p>
  <p class="simpara">
   Comme résultat d&#039;un sous-classement, il est possible de
   redéfinir uniquement les méthodes sélectionnées, et l&#039;on
   peut choisir d&#039;avoir des actions &quot;pre&quot; ou &quot;post&quot;.
  </p>
  <p class="simpara">
   <strong>Construction d&#039;une classe : mysqlnd_plugin_connection::connect()</strong>
  </p>
  <div class="example-contents">
<div class="cdata"><pre>
/*  my_mysqlnd_plugin_classes.c */

 PHP_METHOD(&quot;mysqlnd_plugin_connection&quot;, connect) {
  /* ... simplifié ! ... */
  zval* mysqlnd_rsrc;
  MYSQLND* conn;
  char* host; int host_len;
  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, &quot;rs&quot;,
    &amp;mysqlnd_rsrc, &amp;host, &amp;host_len) == FAILURE) {
    RETURN_NULL();
  }
  ZEND_FETCH_RESOURCE(conn, MYSQLND* conn, &amp;mysqlnd_rsrc, -1,
    &quot;Mysqlnd Connection&quot;, le_mysqlnd_plugin_conn);
  if (PASS == org_methods.connect(conn, host, /* simplifié! */ TSRMLS_CC))
    RETVAL_TRUE;
  else
    RETVAL_FALSE;
}
</pre></div>
  </div>

 </div><?php manual_footer($setup); ?>