mysqlnd-php-plugin-php-architecture-5

  • MySQL Native Driver Plugin
    API
  • MySQL Native Driver Plugin Architecture

  • MySQL Native Driver Plugin Architecture
  • MySQL Native Driver Plugin Architecture

    MySQL Native Driver Plugin Architecture

    This section provides an overview of the
    mysqlnd plugin architecture.

    MySQL Native Driver
    Overview

    Before developing mysqlnd plugins, it is
    useful to know a little of how mysqlnd itself is
    organized. Mysqlnd consists of the following modules:

    The mysqlnd organization chart, per
    module
    Modules Statistics mysqlnd_statistics.c
    Connection mysqlnd.c
    Resultset mysqlnd_result.c
    Resultset Metadata mysqlnd_result_meta.c
    Statement mysqlnd_ps.c
    Network mysqlnd_net.c
    Wire protocol mysqlnd_wireprotocol.c

    C Object Oriented
    Paradigm

    At the code level, mysqlnd uses a C
    pattern for implementing object orientation.

    In C you use a struct to represent an
    object. Members of the struct represent object properties. Struct
    members pointing to functions represent methods.

    Unlike with other languages such as C++ or Java,
    there are no fixed rules on inheritance in the C object oriented
    paradigm. However, there are some conventions that need to be
    followed that will be discussed later.

    The PHP Life Cycle

    When considering the PHP life cycle there are two
    basic cycles:

    • PHP engine startup and shutdown cycle

    • Request cycle

    When the PHP engine starts up it will call the
    module initialization (MINIT) function of each registered
    extension. This allows each module to setup variables and allocate
    resources that will exist for the lifetime of the PHP engine
    process. When the PHP engine shuts down it will call the module
    shutdown (MSHUTDOWN) function of each extension.

    During the lifetime of the PHP engine it will
    receive a number of requests. Each request constitutes another life
    cycle. On each request the PHP engine will call the request
    initialization function of each extension. The extension can
    perform any variable setup and resource allocation required for
    request processing. As the request cycle ends the engine calls the
    request shutdown (RSHUTDOWN) function of each extension so the
    extension can perform any cleanup required.

    How a plugin works

    A mysqlnd plugin works by intercepting
    calls made to mysqlnd by extensions that use
    mysqlnd. This is achieved by obtaining the
    mysqlnd function table, backing it up, and replacing it by
    a custom function table, which calls the functions of the plugin as
    required.

    The following code shows how the mysqlnd
    function table is replaced:

    /* a place to store original function table */
    struct st_mysqlnd_conn_methods org_methods;
    
    void minit_register_hooks(TSRMLS_D) {
      /* active function table */
      struct st_mysqlnd_conn_methods * current_methods
        = mysqlnd_conn_get_methods();
    
      /* backup original function table */
      memcpy(&org_methods, current_methods,
        sizeof(struct st_mysqlnd_conn_methods);
    
      /* install new methods */
      current_methods->query = MYSQLND_METHOD(my_conn_class, query);
    }
    

    Connection function table manipulations must be
    done during Module Initialization (MINIT). The function table is a
    global shared resource. In an multi-threaded environment, with a
    TSRM build, the manipulation of a global shared resource during the
    request processing will almost certainly result in conflicts.

    Note:

    Do not use any fixed-size logic when manipulating
    the mysqlnd function table: new methods may be added at
    the end of the function table. The function table may change at any
    time in the future.

    Calling parent
    methods

    If the original function table entries are backed
    up, it is still possible to call the original function table
    entries – the parent methods.

    In some cases, such as for
    Connection::stmt_init(), it is vital to call the parent
    method prior to any other activity in the derived method.

    MYSQLND_METHOD(my_conn_class, query)(MYSQLND *conn,
      const char *query, unsigned int query_len TSRMLS_DC) {
    
      php_printf("my_conn_class::query(query = %s)\n", query);
    
      query = "SELECT 'query rewritten' FROM DUAL";
      query_len = strlen(query);
    
      return org_methods.query(conn, query, query_len); /* return with call to parent */
    }
    

    Extending properties

    A mysqlnd object is represented by a C
    struct. It is not possible to add a member to a C struct at run
    time. Users of mysqlnd objects cannot simply add
    properties to the objects.

    Arbitrary data (properties) can be added to a
    mysqlnd objects using an appropriate function of the
    mysqlnd_plugin_get_plugin_<object>_data() family.
    When allocating an object mysqlnd reserves space at the
    end of the object to hold a void * pointer to arbitrary
    data. mysqlnd reserves space for one void *
    pointer per plugin.

    The following table shows how to calculate the
    position of the pointer for a specific plugin:

    Pointer calculations for
    mysqlnd
    Memory address Contents
    0 Beginning of the mysqlnd object C struct
    n End of the mysqlnd object C struct
    n + (m x sizeof(void*)) void* to object data of the m-th plugin

    If you plan to subclass any of the mysqlnd
    object constructors, which is allowed, you must keep this in
    mind!

    The following code shows extending properties:

    /* any data we want to associate */
    typedef struct my_conn_properties {
      unsigned long query_counter;
    } MY_CONN_PROPERTIES;
    
    /* plugin id */
    unsigned int my_plugin_id;
    
    void minit_register_hooks(TSRMLS_D) {
      /* obtain unique plugin ID */
      my_plugin_id = mysqlnd_plugin_register();
      /* snip - see Extending Connection: methods */
    }
    
    static MY_CONN_PROPERTIES** get_conn_properties(const MYSQLND *conn TSRMLS_DC) {
      MY_CONN_PROPERTIES** props;
      props = (MY_CONN_PROPERTIES**)mysqlnd_plugin_get_plugin_connection_data(
        conn, my_plugin_id);
      if (!props || !(*props)) {
        *props = mnd_pecalloc(1, sizeof(MY_CONN_PROPERTIES), conn->persistent);
        (*props)->query_counter = 0;
      }
      return props;
    }
    

    The plugin developer is responsible for the
    management of plugin data memory.

    Use of the mysqlnd memory allocator is
    recommended for plugin data. These functions are named using the
    convention: mnd_*loc(). The mysqlnd allocator has
    some useful features, such as the ability to use a debug allocator
    in a non-debug build.

    When and how to subclass
      When to subclass? Each instance has its own private function table? How to subclass?
    Connection (MYSQLND) MINIT No mysqlnd_conn_get_methods()
    Resultset (MYSQLND_RES) MINIT or later Yes mysqlnd_result_get_methods() or object method function table
    manipulation
    Resultset Meta (MYSQLND_RES_METADATA) MINIT No mysqlnd_result_metadata_get_methods()
    Statement (MYSQLND_STMT) MINIT No mysqlnd_stmt_get_methods()
    Network (MYSQLND_NET) MINIT or later Yes mysqlnd_net_get_methods() or object method function table
    manipulation
    Wire protocol (MYSQLND_PROTOCOL) MINIT or later Yes mysqlnd_protocol_get_methods() or object method function table
    manipulation

    You must not manipulate function tables at any time
    later than MINIT if it is not allowed according to the above
    table.

    Some classes contain a pointer to the method
    function table. All instances of such a class will share the same
    function table. To avoid chaos, in particular in threaded
    environments, such function tables must only be manipulated during
    MINIT.

    Other classes use copies of a globally shared
    function table. The class function table copy is created together
    with the object. Each object uses its own function table. This
    gives you two options: you can manipulate the default function
    table of an object at MINIT, and you can additionally refine
    methods of an object without impacting other instances of the same
    class.

    The advantage of the shared function table approach
    is performance. There is no need to copy a function table for each
    and every object.

    Constructor status
    Type Allocation, construction, reset Can be modified? Caller
    Connection (MYSQLND) mysqlnd_init() No mysqlnd_connect()
    Resultset(MYSQLND_RES)

    Allocation:

    • Connection::result_init()

    Reset and re-initialized during:

    • Result::use_result()

    • Result::store_result

    Yes, but call parent!
    • Connection::list_fields()

    • Statement::get_result()

    • Statement::prepare() (Metadata only)

    • Statement::resultMetaData()

    Resultset Meta (MYSQLND_RES_METADATA) Connection::result_meta_init() Yes, but call parent! Result::read_result_metadata()
    Statement (MYSQLND_STMT) Connection::stmt_init() Yes, but call parent! Connection::stmt_init()
    Network (MYSQLND_NET) mysqlnd_net_init() No Connection::init()
    Wire protocol (MYSQLND_PROTOCOL) mysqlnd_protocol_init() No Connection::init()

    It is strongly recommended that you do not entirely
    replace a constructor. The constructors perform memory allocations.
    The memory allocations are vital for the mysqlnd plugin
    API and the object logic of mysqlnd. If you do not care
    about warnings and insist on hooking the constructors, you should
    at least call the parent constructor before doing anything in your
    constructor.

    Regardless of all warnings, it can be useful to
    subclass constructors. Constructors are the perfect place for
    modifying the function tables of objects with non-shared object
    tables, such as Resultset, Network, Wire Protocol.

    Destruction status
    Type Derived method must call parent? Destructor
    Connection yes, after method execution free_contents(), end_psession()
    Resultset yes, after method execution free_result()
    Resultset Meta yes, after method execution free()
    Statement yes, after method execution dtor(), free_stmt_content()
    Network yes, after method execution free()
    Wire protocol yes, after method execution free()

    The destructors are the appropriate place to free
    properties, mysqlnd_plugin_get_plugin_<object>_data().

    The listed destructors may not be equivalent to the
    actual mysqlnd method freeing the object itself. However,
    they are the best possible place for you to hook in and free your
    plugin data. As with constructors you may replace the methods
    entirely but this is not recommended. If multiple methods are
    listed in the above table you will need to hook all of the listed
    methods and free your plugin data in whichever method is called
    first by mysqlnd.

    The recommended method for plugins is to simply
    hook the methods, free your memory and call the parent
    implementation immediately following this.

    Caution

    Due to a bug in PHP versions 5.3.0 to 5.3.3,
    plugins do not associate plugin data with a persistent connection.
    This is because ext/mysql and ext/mysqli do not
    trigger all the necessary mysqlnd end_psession()
    method calls and the plugin may therefore leak memory. This has
    been fixed in PHP 5.3.4.