MySQL Native Driver Plugin Architecture

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.

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.

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 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.

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 mysqlndend_psession() method calls and the plugin may
therefore leak memory. This has been fixed in PHP 5.3.4.