MySQL and PHP
Copyright 1997-2021 the PHP Documentation Group.
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:
Table 7.8 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.
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:
Table 7.9 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.
Table 7.10 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.
Table 7.11 Constructor status
Type | Allocation, construction, reset | Can be modified? | Caller |
---|---|---|---|
Connection (MYSQLND) | mysqlnd_init() | No | mysqlnd_connect() |
Resultset(MYSQLND_RES) | Allocation:
Reset and re-initialized during:
| Yes, but call parent! |
|
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.
Table 7.12 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.