Files
php-doc-ru/reference/mysqlnd/plugin.xml
2024-09-16 13:54:20 +03:00

1442 lines
58 KiB
XML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?xml version="1.0" encoding="utf-8"?>
<!-- EN-Revision: e50e79746736dbdfbabe9bd3566793b3ddf38f58 Maintainer: rjhdby Status: ready -->
<!-- Reviewed: no -->
<chapter xml:id="mysqlnd.plugin" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>API для плагинов к встроенному драйверу MySQL</title>
<para>
API-интерфейс для плагинов к встроенному драйверу MySQL — особенность встроенного
драйвера MySQL — <literal>mysqlnd</literal>. Плагины <literal>mysqlnd</literal>
работают на уровне между PHP-приложениями и MySQL-сервером. Это похоже на работу
MySQL Proxy. Плагины <literal>mysqlnd</literal> реализуют стандартные
задачи MySQL Proxy наподобие балансировки нагрузки, мониторинга и оптимизации
быстродействия. При этом, вследствие другой архитектуры и месторасположения,
плагины <literal>mysqlnd</literal> не содержат отдельных недочётов MySQL Proxy. Например,
нет единой точки отказа, не требуется установка отдельного proxy-сервера и не нужно
изучать новый язык программирования Lua.
</para>
<para>
Плагин <literal>mysqlnd</literal> рассматривают как модуль
<literal>mysqlnd</literal>. Плагины умеют перехватывать большую часть функций
драйвера <literal>mysqlnd</literal>. Эти функции вызывают модули PHP наподобие
<literal>ext/mysql</literal>, <literal>ext/mysqli</literal> и <literal>PDO_MYSQL</literal>.
В конечном счёте, плагинам драйвера <literal>mysqlnd</literal> доступен перехват каждого запроса,
который сделали эти модули из клиентского приложения.
</para>
<para>
Внутренние вызовы функций <literal>mysqlnd</literal> также можно перехватить
или заменить. Ограничений по работе с внутренней таблицей функций <literal>mysqlnd</literal>
нет. Возможно настроить всё так, что при вызове отдельных функций
<literal>mysqlnd</literal> из модулей, которые работают с <literal>mysqlnd</literal>,
этот вызов перенаправится в соответствующую функцию плагина
<literal>mysqlnd</literal>. Возможность манипулировать внутренней таблицей функций
<literal>mysqlnd</literal> даёт плагинам максимум гибкости.
</para>
<para>
Плагины <literal>mysqlnd</literal> — фактически модули PHP, которые написали
на языке C и которые работают через API-интерфейс для плагинов <literal>mysqlnd</literal>
(встроенном в драйвер <literal>mysqlnd</literal>). Плагины могут быть полностью прозрачными
для PHP-приложений. Изменять приложение не потребуется, поскольку плагины работают
на другом уровне. Считают, что плагины <literal>mysqlnd</literal>
работают на уровень ниже драйвера <literal>mysqlnd</literal>.
</para>
<para>
Нижеуказанный список показывает несколько возможных
вариантов плагинов <literal>mysqlnd</literal>.
</para>
<itemizedlist>
<listitem>
<para>
Балансировка нагрузки
</para>
<itemizedlist>
<listitem>
<para>
Разделение чтения и записи. Примером является модуль
PECL/mysqlnd_ms (Master Slave). Модель разделяет
запросы на чтение и запись для настройки репликации.
</para>
</listitem>
<listitem>
<para>
Отказоустойчивость
</para>
</listitem>
<listitem>
<para>
Равномерная загрузка, запросы на наименее загруженный сервер
</para>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para>
Мониторинг
</para>
<itemizedlist>
<listitem>
<para>
Логирование запросов
</para>
</listitem>
<listitem>
<para>
Анализ запросов
</para>
</listitem>
<listitem>
<para>
Аудит запросов. Пример — модуль
PECL/mysqlnd_sip (SQL Injection Protection, защита
от SQL-инъекций). Модель анализирует запросы
и выполняет только те, которые подходят под набор правил.
</para>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para>
Производительность.
</para>
<itemizedlist>
<listitem>
<para>
Кеширование. Пример — модуль
PECL/mysqlnd_qc (Query Cache, кеширование запросов).
</para>
</listitem>
<listitem>
<para>
Ограничение ресурсов выделяемых запросу
</para>
</listitem>
<listitem>
<para>
Шардинг. Пример — модуль PECL/mysqlnd_mc
(Multi Connect). Модель пытается разбить запрос
SELECT на n частей через SELECT ... LIMIT part_1,
SELECT LIMIT part_n. Он отправляет запросы
на отдельные MySQL-серверы и собирает результат на клиенте.
</para>
</listitem>
</itemizedlist>
</listitem>
</itemizedlist>
<para>
<emphasis role="bold">Доступные плагины к встроенному драйверу MySQL</emphasis>
</para>
<para>
Существует несколько уже доступных плагинов mysqlnd. Список включает:
</para>
<itemizedlist>
<listitem>
<para>
<emphasis role="bold">PECL/mysqlnd_mc</emphasis> — Multi
Connect plugin.
</para>
</listitem>
<listitem>
<para>
<emphasis role="bold">PECL/mysqlnd_ms</emphasis> — Master
Slave plugin.
</para>
</listitem>
<listitem>
<para>
<emphasis role="bold">PECL/mysqlnd_qc</emphasis> — Query
Cache (кеширование запросов) plugin.
</para>
</listitem>
<listitem>
<para>
<emphasis role="bold">PECL/mysqlnd_pscache</emphasis> -
Prepared Statement Handle Cache plugin (обработка
кеширования подготовленных запросов)
</para>
</listitem>
<listitem>
<para>
<emphasis role="bold">PECL/mysqlnd_sip</emphasis> — SQL
Injection Protection plugin (защита от SQL-инъекций)
</para>
</listitem>
<listitem>
<para>
<emphasis role="bold">PECL/mysqlnd_uh</emphasis> — User
Handler plugin (обработка пользователей)
</para>
</listitem>
</itemizedlist>
<section xml:id="mysqlnd.plugin.mysql-proxy">
<title>Сравнение плагинов mysqlnd с MySQL Proxy</title>
<para>
Плагины <literal>mysqlnd</literal> и MySQL Proxy — разные
технологии, использующие разные подходы. Оба варианта
являются подходящими инструментами для решения
разнообразных стандартных задач, таких как балансировка
нагрузки, мониторинг и улучшение производительности.
Важным отличием является то, что MySQL Proxy работает со
всеми клиентами MySQL, тогда как плагины
<literal>mysqlnd</literal> — только для PHP-приложений.
</para>
<para>
Как модуль PHP, плагин <literal>mysqlnd</literal>
устанавливается на сервере приложений PHP вместе с
остальным PHP. MySQL Proxy может быть запущен на сервере
приложений PHP или же быть установлен на отдельной машине
для поддержки множественных серверов приложений PHP.
</para>
<para>
Установка MySQL Proxy на сервере приложений имеет два преимущества:
</para>
<orderedlist>
<listitem>
<para>
Отсутствие единой точки отказа
</para>
</listitem>
<listitem>
<para>
Лёгкость в масштабировании (горизонтальном или же клиентском)
</para>
</listitem>
</orderedlist>
<para>
MySQL Proxy (а также плагины <literal>mysqlnd</literal>)
могут легко решать проблемы, ради решения которых иначе
понадобились бы изменения в существующих приложениях.
</para>
<para>
Тем не менее, у MySQL Proxy есть некоторые недостатки:
</para>
<itemizedlist>
<listitem>
<para>
MySQL Proxy — новый элемент и технология, которую потребуется изучить и установить
</para>
</listitem>
<listitem>
<para>
MySQL Proxy требует знания скриптового языка Lua
</para>
</listitem>
</itemizedlist>
<para>
MySQL Proxy может быть изменён с помощью C и Lua. Lua
является предпочтительным скриптовым языком для MySQL
Proxy. Для большинства экспертов PHP, Lua является новым
языком, который надо изучать. Плагин
<literal>mysqlnd</literal> может быть написан на C. Также
можно написать плагин на PHP, используя <link xlink:href="http://pecl.php.net/package/mysqlnd_uh">PECL/mysqlnd_uh</link>.
</para>
<para>
MySQL Proxy работает как демон — фоновый процесс. MySQL Proxy может вспомнить ранее
принятые решения, так как все состояние может быть сохранено. Однако плагины
<literal>mysqlnd</literal> привязаны к жизненному циклу PHP, базирующемуся на
запросах. Также MySQL Proxy может разделять единожды полученный результат между
разными серверами приложений. Плагины <literal>mysqlnd</literal> для решения этой
задачи должны использовать какое-нибудь постоянное хранилище, для сохранения
результатов между запросами. Например, для этого может быть использован другой демон,
такой как Memcache. Так что в этом случае MySQL Proxy работает явно лучше.
</para>
<para>
MySQL Proxy работает поверх сетевых протоколов. С помощью
MySQL Proxy вы можете разобрать и подвергнуть инженерному
анализу протокол MySQL Client Server. Что-либо поменять
можно только манипулируя протоколом обмена. Если протокол
вдруг поменяется (что бывает крайне редко), скрипты
MySQL Proxy будет необходимо переписывать.
</para>
<para>
Плагины <literal>Mysqlnd</literal> работают поверх C API,
который дублирует клиент <literal>libmysqlclient</literal>. Этот API-интерфейс языка C по сути обычная обёртка вокруг
протокола MySQL Client Server. Вы можете перехватывать каждый
вызов API-интерфейса C. PHP использует API-интерфейс C, фактически можно
перехватывать вообще каждый вызов PHP, без программирования на уровне протокола обмена.
</para>
<para>
<literal>Mysqlnd</literal> реализует протокол обмена.
Таким образом, плагины могут перехватывать, исследовать, менять
и даже целиком заменять протокол связи. Хотя
обычно ничего этого не требуется.
</para>
<para>
Плагины позволяют вам использовать два уровня (C API и
протокол обмена), в этом они гораздо гибче, чем MySQL
Proxy. Если плагин <literal>mysqlnd</literal> реализован с
использованием C API, изменения протокола обмена не
потребуют изменения плагина.
</para>
</section>
<section xml:id="mysqlnd.plugin.obtaining">
<title>Получение API плагинов mysqlnd</title>
<para>
API плагинов <literal>mysqlnd</literal> является частью
стандартного модуля <literal>ext/mysqlnd</literal>.
API плагинов <literal>mysqlnd</literal> начали разрабатывать
в декабре 2009 года. Он разрабатывался как часть
репозитория исходных кодов PHP и, соответственно,
доступен через публичный репозиторий Git либо через
загрузку снапшота исходных кодов.
</para>
<para>
Разработчики плагинов узнают версию <literal>mysqlnd</literal> путём доступа
к <literal>MYSQLND_VERSION</literal> — строка
в формате <quote>mysqlnd 5.0.7-dev - 091210 - $Revision: 300535</quote> или через
<literal>MYSQLND_VERSION_ID</literal> — числовое представление версии
наподобие 50007. Версию из этого числа рассчитывают вот так:
</para>
<table xml:id="mysqlnd.plugin.version-id">
<title>Таблица расчёта MYSQLND_VERSION_ID</title>
<tgroup cols="2">
<thead>
<row>
<entry>Версия (часть)</entry>
<entry>Пример</entry>
</row>
</thead>
<tbody>
<row>
<entry>Major*10000</entry>
<entry>5*10000 = 50000</entry>
</row>
<row>
<entry>Minor*100</entry>
<entry>0*100 = 0</entry>
</row>
<row>
<entry>Patch</entry>
<entry>7 = 7</entry>
</row>
<row>
<entry>MYSQLND_VERSION_ID</entry>
<entry>50007</entry>
</row>
</tbody>
</tgroup>
</table>
<para>
В процессе разработки, разработчики должны отслеживать версию
<literal>mysqlnd</literal> на предмет совместимости и версионного тестирования, так как
версии <literal>mysqlnd</literal> имеют свойство меняться и это
может случиться в процессе работы над плагином.
</para>
</section>
<section xml:id="mysqlnd.plugin.architecture">
<title>Архитектура плагинов MySQL Native Driver </title>
<para>
В этой секции рассмотрена архитектура плагинов
<literal>mysqlnd</literal>.
</para>
<para>
<emphasis role="bold">Обзор MySQL Native Driver</emphasis>
</para>
<para>
Перед началом разработки плагинов
<literal>mysqlnd</literal>, полезно ознакомиться, как сам по
себе организован <literal>mysqlnd</literal>.
<literal>Mysqlnd</literal> состоит из следующих модулей:
</para>
<table xml:id="mysqlnd.plugin.orgchart">
<title>Организационная схема mysqlnd, помодульно</title>
<tgroup cols="2">
<thead>
<row>
<entry>Модули статистики</entry>
<entry>mysqlnd_statistics.c</entry>
</row>
</thead>
<tbody>
<row>
<entry>Соединение</entry>
<entry>mysqlnd.c</entry>
</row>
<row>
<entry>Результирующий набор</entry>
<entry>mysqlnd_result.c</entry>
</row>
<row>
<entry>Метаданные результирующего набора</entry>
<entry>mysqlnd_result_meta.c</entry>
</row>
<row>
<entry>Оператор</entry>
<entry>mysqlnd_ps.c</entry>
</row>
<row>
<entry>Сеть</entry>
<entry>mysqlnd_net.c</entry>
</row>
<row>
<entry>Протокол обмена</entry>
<entry>mysqlnd_wireprotocol.c</entry>
</row>
</tbody>
</tgroup>
</table>
<para>
<emphasis role="bold">Объектно-ориентированная парадигма C</emphasis>
</para>
<para>
На уровне кода, <literal>mysqlnd</literal> использует
паттерн C для реализации объектно-ориентированного подхода.
</para>
<para>
В C объекты описывают используя <literal>struct</literal>.
Члены структуры являются свойствами объекта.
Члены структуры, указывающие на функции, являются
методами.
</para>
<para>
В отличие от таких языков как C++ или Java, в C нет фиксированных правил наследования.
Однако существуют некоторые договорённости, которым необходимо следовать,
но это мы обсудим позже.
</para>
<para>
<emphasis role="bold">Жизненный цикл PHP</emphasis>
</para>
<para>
При рассмотрении жизненного цикла PHP существует два основных цикла:
</para>
<itemizedlist>
<listitem>
<para>
Цикл старта и остановки движка PHP
</para>
</listitem>
<listitem>
<para>
Цикл обработки запроса
</para>
</listitem>
</itemizedlist>
<para>
При старте движка PHP, первым делом вызывается функция
инициализации модулей (MINIT) для каждого зарегистрированного модуля. Это
позволяет каждому модулю установить переменные и выделить ресурсы,
которые будут задействованы все время жизни процесса
движка PHP. Когда движок PHP выключается, он вызывает
функцию остановки модулей (MSHUTDOWN) для каждого модуля.
</para>
<para>
На протяжении жизненного цикла движка PHP, он принимает некоторое количество
запросов. Каждый запрос порождает новый жизненный цикл. На каждый запрос, движок PHP
вызывает функцию инициализации для каждого модуля. Модуль может предпринять
выставление переменных и выделение ресурсов, требуемых для обслуживания запроса.
По окончании жизни запроса, движок вызывает функцию остановки запроса (RSHUTDOWN)
для каждого модуля, что позволяет им произвести необходимые чистки.
</para>
<para>
<emphasis role="bold">Как работает плагин</emphasis>
</para>
<para>
Плагин <literal>mysqlnd</literal> работает перехватывая
вызовы модулей, использующих <literal>mysqlnd</literal>,
к <literal>mysqlnd</literal>. Это достигается подменой
таблицы функций <literal>mysqlnd</literal> на созданную
плагином.
</para>
<para>
Следующий код демонстрирует замену таблицы функций
<literal>mysqlnd</literal>:
</para>
<programlisting>
<![CDATA[
/* хранилище оригинальной таблицы */
struct st_mysqlnd_conn_methods org_methods;
void minit_register_hooks(TSRMLS_D) {
/* активная таблица функций */
struct st_mysqlnd_conn_methods * current_methods
= mysqlnd_conn_get_methods();
/* бэкап оригинальной таблицы */
memcpy(&org_methods, current_methods,
sizeof(struct st_mysqlnd_conn_methods);
/* установка новых методов */
current_methods->query = MYSQLND_METHOD(my_conn_class, query);
}
]]>
</programlisting>
<para>
Манипуляцией с таблицей функций соединения необходимо
заниматься на этапе инициализации модуля (MINIT). Таблица
функций — глобальный разделяемый ресурс.
В многопоточном окружении, со сборкой TSRM, манипуляция
глобальным разделяемым ресурсом на этапе обработки
запроса вызовет конфликты.
</para>
<note>
<para>
Не используйте логику, которая связана
с фиксированным размером при манипуляции с таблицей
функций <literal>mysqlnd</literal>. Всегда добавляйте
новые методы в конец таблицы, так как сама таблица может
в будущем в любой момент измениться.
</para>
</note>
<para>
<emphasis role="bold">Вызов родительских методов</emphasis>
</para>
<para>
Если записи оригинальной таблицы функций сохранились,
остаётся возможность вызвать оригинальный метод — родительский.
</para>
<para>
В отдельных случаях, например, для <literal>Connection::stmt_init()</literal>, жизненно
важно сначала вызвать родительский метод, и только потом делать
что-либо в новом методе.
</para>
<programlisting>
<![CDATA[
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); /* возврат с вызовом родителя */
}
]]>
</programlisting>
<para>
<emphasis role="bold">Расширение свойств</emphasis>
</para>
<para>
Объекты <literal>mysqlnd</literal> представлены как C struct.
Невозможно добавить члена в C struct во время исполнения.
Пользователи объектов <literal>mysqlnd</literal> не могут
просто добавить свойства объекту.
</para>
<para>
Произвольные данные (свойства) могут быть добавлены к
объекту <literal>mysqlnd</literal> с использованием
соответствующей функции из семейства
<literal>mysqlnd_plugin_get_plugin_&lt;object&gt;_data()</literal>.
При размещении объекта <literal>mysqlnd</literal>
резервируется место в конце объекта для удержания
<literal>void *</literal> указателя на произвольные данные.
<literal>mysqlnd</literal> резервирует место для одного
<literal>void *</literal> указателя на плагин.
</para>
<para>
В следующей таблице показано, как вычислить положение указателя для конкретного плагина:
</para>
<table xml:id="mysqlnd.plugin.pointercalc">
<title>Расчёт указателя для mysqlnd</title>
<tgroup cols="2">
<thead>
<row>
<entry>Адрес памяти</entry>
<entry>Содержимое</entry>
</row>
</thead>
<tbody>
<row>
<entry>0</entry>
<entry>Начало объекта mysqlnd (C struct)</entry>
</row>
<row>
<entry>n</entry>
<entry>Конец объекта mysqlnd (C struct)</entry>
</row>
<row>
<entry>n + (m x sizeof(void*))</entry>
<entry>void* для данных объекта плагина номер m</entry>
</row>
</tbody>
</tgroup>
</table>
<para>
Если вы планируете делать подкласс от одного из конструкторов объекта
<literal>mysqlnd</literal>, которые разрешены, имейте это в виду!
</para>
<para>
Следующий код демонстрирует расширение свойств:
</para>
<programlisting>
<![CDATA[
/* Любые данные, которые мы хотим добавить */
typedef struct my_conn_properties {
unsigned long query_counter;
} MY_CONN_PROPERTIES;
/* идентификатор плагина */
unsigned int my_plugin_id;
void minit_register_hooks(TSRMLS_D) {
/* получаем уникальный идентификатор плагина */
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;
}
]]>
</programlisting>
<para>
Разработчик плагина отвечает за управление памятью данных плагина.
</para>
<para>
Рекомендуется использовать управление памятью <literal>mysqlnd</literal> для данных
плагина. Эти функции именуются используя такие соглашения:
<literal>mnd_*loc()</literal>. Управление памятью <literal>mysqlnd</literal> имеет
ряд полезных свойств, таких как использование отладочного
модуля управления памятью в неотладочных сборках.
</para>
<table xml:id="mysqlnd.plugin.subclass">
<title>Когда и как создавать подкласс</title>
<tgroup cols="4">
<thead>
<row>
<entry></entry>
<entry>Когда создавать подкласс?</entry>
<entry>Каждый экземпляр имеет свою собственную таблицу функций?</entry>
<entry>Как создавать подкласс?</entry>
</row>
</thead>
<tbody>
<row>
<entry>Соединение (MYSQLND)</entry>
<entry>MINIT</entry>
<entry>Нет</entry>
<entry>mysqlnd_conn_get_methods()</entry>
</row>
<row>
<entry>Результирующий набор (MYSQLND_RES)</entry>
<entry>MINIT or later</entry>
<entry>Да</entry>
<entry>mysqlnd_result_get_methods() или методом объекта,
манипулирующим таблицей функций</entry>
</row>
<row>
<entry>Результирующий набор (MYSQLND_RES_METADATA)</entry>
<entry>MINIT</entry>
<entry>Нет</entry>
<entry>mysqlnd_result_metadata_get_methods()</entry>
</row>
<row>
<entry>Оператор (MYSQLND_STMT)</entry>
<entry>MINIT</entry>
<entry>Нет</entry>
<entry>mysqlnd_stmt_get_methods()</entry>
</row>
<row>
<entry>Сеть (MYSQLND_NET)</entry>
<entry>MINIT или позже</entry>
<entry>Да</entry>
<entry>mysqlnd_net_get_methods() или методом объекта,
манипулирующим таблицей функций</entry>
</row>
<row>
<entry>Протокол обмена (MYSQLND_PROTOCOL)</entry>
<entry>MINIT или позже</entry>
<entry>Да</entry>
<entry>mysqlnd_protocol_get_methods() или методом
объекта, манипулирующим таблицей функций</entry>
</row>
</tbody>
</tgroup>
</table>
<para>
Вы не должны манипулировать таблицей функций после
MINIT, если это прямо не разрешено в таблице выше.
</para>
<para>
Некоторые классы содержат указатель на таблицу функций методов. Все экземпляры
подобных классов должны делить одну и ту же таблицу функций. Для того, чтобы избежать
хаоса, особенно в многопоточном окружении, управлять такими таблицами функций стоит
только во время MINIT.
</para>
<para>
Прочие классы используют копии глобально разделённых таблиц функций. Таблица функций
создаётся одновременно с объектом. Каждый объект использует свою таблицу. Это даёт вам
две возможности: вы можете управлять таблицей функций по умолчанию для объекта во
время MINIT, а также вы можете изменять методы объекта не затрагивая другие
экземпляры этого же класса.
</para>
<para>
Преимущество разделяемой таблицы функций в производительности, так как нет нужды
копировать таблицу функций отдельно для каждого объекта.
</para>
<table xml:id="mysqlnd.plugin.constatus">
<title>Статус конструктора</title>
<tgroup cols="4">
<thead>
<row>
<entry>Тип</entry>
<entry>Размещение, создание, сброс</entry>
<entry>Может быть изменено?</entry>
<entry>Вызывающий</entry>
</row>
</thead>
<tbody>
<row>
<entry>Connection (MYSQLND)</entry>
<entry>mysqlnd_init()</entry>
<entry>Нет</entry>
<entry>mysqlnd_connect()</entry>
</row>
<row>
<entry>Результирующий набор (MYSQLND_RES)</entry>
<entry><para>
Размещение:
</para>
<itemizedlist>
<listitem>
<para>
Connection::result_init()
</para>
</listitem>
</itemizedlist>
<para>
Сброс и повторная инициализация во время:
</para>
<itemizedlist>
<listitem>
<para>
Result::use_result()
</para>
</listitem>
<listitem>
<para>
Result::store_result
</para>
</listitem>
</itemizedlist></entry>
<entry>Да, но вызовите родителя!</entry>
<entry><itemizedlist>
<listitem>
<para>
Connection::list_fields()
</para>
</listitem>
<listitem>
<para>
Statement::get_result()
</para>
</listitem>
<listitem>
<para>
Statement::prepare() (Только метаданные)
</para>
</listitem>
<listitem>
<para>
Statement::resultMetaData()
</para>
</listitem>
</itemizedlist></entry>
</row>
<row>
<entry>Метаданные результирующего набора (MYSQLND_RES_METADATA)</entry>
<entry>Connection::result_meta_init()</entry>
<entry>Да, но вызовите родителя!</entry>
<entry>Result::read_result_metadata()</entry>
</row>
<row>
<entry>Оператор (MYSQLND_STMT)</entry>
<entry>Connection::stmt_init()</entry>
<entry>Да, но вызовите родителя!</entry>
<entry>Connection::stmt_init()</entry>
</row>
<row>
<entry>Сеть (MYSQLND_NET)</entry>
<entry>mysqlnd_net_init()</entry>
<entry>Нет</entry>
<entry>Connection::init()</entry>
</row>
<row>
<entry>Протокол обмена (MYSQLND_PROTOCOL)</entry>
<entry>mysqlnd_protocol_init()</entry>
<entry>Нет</entry>
<entry>Connection::init()</entry>
</row>
</tbody>
</tgroup>
</table>
<para>
Настоятельно рекомендуется не заменять конструктор
целиком. Конструкторы производят выделение памяти.
Выделение памяти жизненно необходимо для API плагинов
<literal>mysqlnd</literal> и для логики объекта
<literal>mysqlnd</literal>. Если вам не страшны
предупреждения и хотите сильно поменять конструктор, то
хотя бы вызовите родительский конструктор прежде, чем что-либо делать.
</para>
<para>
Несмотря на предупреждения, это бывает полезным для конструктора подкласса.
Конструкторы — отличное место для изменения таблицы функций для объектов,
которые не работают с разделённой таблицу, например,
результирующий набор, сеть, протокол обмена.
</para>
<table xml:id="mysqlnd.plugin.deststatus">
<title>Статус уничтожения</title>
<tgroup cols="3">
<thead>
<row>
<entry></entry>
<entry>Производный метод должен вызвать родительский?</entry>
<entry>Деструктор</entry>
</row>
</thead>
<tbody>
<row>
<entry>Соединение</entry>
<entry>да, после выполнения метода</entry>
<entry>free_contents(), end_psession()</entry>
</row>
<row>
<entry>Результирующий набор</entry>
<entry>да, после выполнения метода</entry>
<entry>free_result()</entry>
</row>
<row>
<entry>Метаданные результирующего набора</entry>
<entry>да, после выполнения метода</entry>
<entry>free()</entry>
</row>
<row>
<entry>Оператор</entry>
<entry>да, после выполнения метода</entry>
<entry>dtor(), free_stmt_content()</entry>
</row>
<row>
<entry>Сеть</entry>
<entry>да, после выполнения метода</entry>
<entry>free()</entry>
</row>
<row>
<entry>Протокол обмена</entry>
<entry>да, после выполнения метода</entry>
<entry>free()</entry>
</row>
</tbody>
</tgroup>
</table>
<para>
Деструкторы являются подходящим местом, чтобы освободить ресурсы, занимаемые
свойствами
<literal>mysqlnd_plugin_get_plugin_<replaceable>&lt;object&gt;</replaceable>_data()</literal>.
</para>
<para>
Перечисленные деструкторы могут не совпадать с актуальными методами
<literal>mysqlnd</literal> для очистки самого объекта. Однако они являются самым лучшим
местом, куда вы можете вклиниться для очистки данных своего плагина. Так же как и
с конструкторами, вы можете полностью переопределить эти методы, но делать это не
рекомендуется. Если вам необходимо вставить в каждый из перечисленных методов
очистку данных своего плагина, то необходимо обеспечить запуск родительских
методов <literal>mysqlnd</literal>.
</para>
<para>
Для плагинов рекомендуют метод — выполнить код очистки данных плагина
и сразу после этого вызывать родительский метод.
</para>
</section>
<section xml:id="mysqlnd.plugin.api">
<title>API плагинов mysqlnd</title>
<para>
API-интерфейс плагинов <literal>mysqlnd</literal> поддерживает следующие функции:
</para>
<itemizedlist>
<listitem>
<para>
mysqlnd_plugin_register()
</para>
</listitem>
<listitem>
<para>
mysqlnd_plugin_count()
</para>
</listitem>
<listitem>
<para>
mysqlnd_plugin_get_plugin_connection_data()
</para>
</listitem>
<listitem>
<para>
mysqlnd_plugin_get_plugin_result_data()
</para>
</listitem>
<listitem>
<para>
mysqlnd_plugin_get_plugin_stmt_data()
</para>
</listitem>
<listitem>
<para>
mysqlnd_plugin_get_plugin_net_data()
</para>
</listitem>
<listitem>
<para>
mysqlnd_plugin_get_plugin_protocol_data()
</para>
</listitem>
<listitem>
<para>
mysqlnd_conn_get_methods()
</para>
</listitem>
<listitem>
<para>
mysqlnd_result_get_methods()
</para>
</listitem>
<listitem>
<para>
mysqlnd_result_meta_get_methods()
</para>
</listitem>
<listitem>
<para>
mysqlnd_stmt_get_methods()
</para>
</listitem>
<listitem>
<para>
mysqlnd_net_get_methods()
</para>
</listitem>
<listitem>
<para>
mysqlnd_protocol_get_methods()
</para>
</listitem>
</itemizedlist>
<para>
Нет стандартных определений того, что такое плагин и как он работает.
</para>
<para>
Часто встречающиеся в плагинах компоненты:
</para>
<itemizedlist>
<listitem>
<para>
Менеджер плагина
</para>
</listitem>
<listitem>
<para>
API плагина
</para>
</listitem>
<listitem>
<para>
Сервисы приложения (или модули)
</para>
</listitem>
<listitem>
<para>
API сервисов приложения (или API модулей)
</para>
</listitem>
</itemizedlist>
<para>
Концепция плагина <literal>mysqlnd</literal> эксплуатирует эту функциональность и,
кроме того, радует нас открытой архитектурой.
</para>
<para>
<emphasis role="bold">Нет запретов </emphasis>
</para>
<para>
Плагин имеет полный доступ ко всем внутренностям <literal>mysqlnd</literal>.
Нет ограничений или запретов, связанных с безопасностью. Все что угодно можно
переписать для реализации дружественных или враждебных алгоритмов, так что
рекомендуется ставить плагины только из доверенных источников.
</para>
<para>
Как обсуждалось выше, плагины могут свободно использовать указатели. Эти указатели
ничем не ограничены и могут указывать на данные другого плагина. Простейшая
арифметическая операция позволит получить доступ к данным другого плагина.
</para>
<para>
Рекомендуется писать сотрудничающие плагины, которые могут работать сообща с другими
плагинами и всегда вызывать родительские методы. Плагины никогда не должны вести себя
враждебно к самому <literal>mysqlnd</literal>.
</para>
<table xml:id="mysqlnd.plugin.chaining">
<title>Проблемы: пример сотрудничества и построения цепочки</title>
<tgroup cols="3">
<thead>
<row>
<entry>Модуль</entry>
<entry>Указатель mysqlnd.query()</entry>
<entry>Стек вызова, если вызывается родитель</entry>
</row>
</thead>
<tbody>
<row>
<entry>ext/mysqlnd</entry>
<entry>mysqlnd.query()</entry>
<entry>mysqlnd.query</entry>
</row>
<row>
<entry>ext/mysqlnd_cache</entry>
<entry>mysqlnd_cache.query()</entry>
<entry><orderedlist>
<listitem>
<para>
mysqlnd_cache.query()
</para>
</listitem>
<listitem>
<para>
mysqlnd.query
</para>
</listitem>
</orderedlist></entry>
</row>
<row>
<entry>ext/mysqlnd_monitor</entry>
<entry>mysqlnd_monitor.query()</entry>
<entry><orderedlist>
<listitem>
<para>
mysqlnd_monitor.query()
</para>
</listitem>
<listitem>
<para>
mysqlnd_cache.query()
</para>
</listitem>
<listitem>
<para>
mysqlnd.query
</para>
</listitem>
</orderedlist></entry>
</row>
</tbody>
</tgroup>
</table>
<para>
В этом сценарии загружены плагины кеша (<literal>ext/mysqlnd_cache</literal>) и
мониторинга (<literal>ext/mysqlnd_monitor</literal>).
Оба наследуют класс <literal>Connection::query()</literal>. регистрация плагинов
происходит на этапе <literal>MINIT</literal> в соответствии с описанной
выше логикой. PHP, по умолчанию, вызывает модули в алфавитном порядке.
Плагины не знают друг о друге и не накладывают каких-либо зависимостей.
</para>
<para>
По умолчанию, плагины вызывают родительский метод query из своей, переопределённой,
версии этого метода.
</para>
<para>
<emphasis role="bold">Резюме по модулю PHP</emphasis>
</para>
<para>
Повторение пройденного материала на примере поведения плагина
<literal>ext/mysqlnd_plugin</literal>, использующего API плагинов
<literal>mysqlnd</literal> для PHP:
</para>
<itemizedlist>
<listitem>
<para>
Любое приложение PHP, использующее MySQL пытается установить соединение по
адресу 192.168.2.29
</para>
</listitem>
<listitem>
<para>
Приложение использует одно из следующих модулей <literal>ext/mysql</literal>,
<literal>ext/mysqli</literal> или <literal>PDO_MYSQL</literal>. Все
три модуля используют <literal>mysqlnd</literal> для соединения с 192.168.2.29.
</para>
</listitem>
<listitem>
<para>
<literal>Mysqlnd</literal> вызывает метод соединения, который наследуется
плагином <literal>ext/mysqlnd_plugin</literal>.
</para>
</listitem>
<listitem>
<para>
<literal>ext/mysqlnd_plugin</literal> вызывает зарегистрированный
пользователем метод <literal>proxy::connect()</literal>.
</para>
</listitem>
<listitem>
<para>
Этот метод подменяет IP адрес соединения с 192.168.2.29
на 127.0.0.1 и возвращает установленное <literal>parent::connect()</literal> соединение.
</para>
</listitem>
<listitem>
<para>
<literal>ext/mysqlnd_plugin</literal> делает то же самое, что и
<literal>parent::connect(127.0.0.1)</literal>, вызывая оригинальный метод
<literal>mysqlnd</literal> для соединения.
</para>
</listitem>
<listitem>
<para>
<literal>ext/mysqlnd</literal> устанавливает соединение и возвращает его
<literal>ext/mysqlnd_plugin</literal>.
<literal>ext/mysqlnd_plugin</literal>, в свою очередь, передаёт его дальше.
</para>
</listitem>
<listitem>
<para>
Без разницы, какой модуль был использован, он всё равно получит
соединение к 127.0.0.1. После этого, модуль возвращает это соединение
приложению. Круг замкнулся.
</para>
</listitem>
</itemizedlist>
</section>
<section xml:id="mysqlnd.plugin.developing">
<title>Начинаем разработку плагина mysqlnd</title>
<para>
Важно помнить, что плагин <literal>mysqlnd</literal> сам по себе
является модулем PHP.
</para>
<para>
Следующий пример показывает базовую структуру функции MINIT,
использующуюся в типичном плагине <literal>mysqlnd</literal>:
</para>
<programlisting>
<![CDATA[
/* my_php_mysqlnd_plugin.c */
static PHP_MINIT_FUNCTION(mysqlnd_plugin) {
/* глобальные переменные, ini-настройки, ресурсы, классы */
/* регистрируем плагин 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->query = MYSQLND_METHOD(mysqlnd_plugin_conn, query);
conn_m->connect = MYSQLND_METHOD(mysqlnd_plugin_conn, connect);
}
]]>
</programlisting>
<programlisting>
<![CDATA[
/* my_mysqlnd_plugin.c */
enum_func_status MYSQLND_METHOD(mysqlnd_plugin_conn, query)(/* ... */) {
/* ... */
}
enum_func_status MYSQLND_METHOD(mysqlnd_plugin_conn, connect)(/* ... */) {
/* ... */
}
]]>
</programlisting>
<para>
<emphasis role="bold">Анализ задачи: от C до пользовательского пространства</emphasis>
</para>
<programlisting>
<![CDATA[
class proxy extends mysqlnd_plugin_connection {
public function connect($host, ...) { .. }
}
mysqlnd_plugin_set_conn_proxy(new proxy());
]]>
</programlisting>
<para>
Процесс:
</para>
<orderedlist>
<listitem>
<para>
PHP: пользователь регистрирует callback-функцию плагина
</para>
</listitem>
<listitem>
<para>
PHP: пользователь вызывает PHP MySQL API для соединения с MySQL
</para>
</listitem>
<listitem>
<para>
C: ext/*mysql* вызывает метод mysqlnd
</para>
</listitem>
<listitem>
<para>
C: mysqlnd обрывается в ext/mysqlnd_plugin
</para>
</listitem>
<listitem>
<para>
C: ext/mysqlnd_plugin
<orderedlist>
<listitem>
<para>
Вызывает пользовательскую callback-функцию
</para>
</listitem>
<listitem>
<para>
Или оригинальный метод <literal>mysqlnd</literal>, если она не задана
</para>
</listitem>
</orderedlist>
</para>
</listitem>
</orderedlist>
<para>
Вам необходимо выполнить следующие действия:
</para>
<orderedlist>
<listitem>
<para>
Создайте в С класс "mysqlnd_plugin_connection"
</para>
</listitem>
<listitem>
<para>
Примите и зарегистрируйте прокси объект с помощью
"mysqlnd_plugin_set_conn_proxy()"
</para>
</listitem>
<listitem>
<para>
Вызовите прокси методы пространства пользователя из C (оптимизация -
zend_interfaces.h)
</para>
</listitem>
</orderedlist>
<para>
Методы объекта пространства пользователя должны быть вызваны с помощью
<literal>call_user_function()</literal> или на уровень ниже, с помощью
<literal>zend_call_method()</literal>.
</para>
<para>
<emphasis role="bold">Оптимизация: вызывайте методы из С с помощью
zend_call_method </emphasis>
</para>
<para>
Следующий кусок кода демонстрирует прототип функции
<literal>zend_call_method</literal>, взятый из
<filename>zend_interfaces.h</filename>.
</para>
<programlisting>
<![CDATA[
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
);
]]>
</programlisting>
<para>
Zend API поддерживает только два аргумента. Вам может понадобиться больше. К примеру:
</para>
<programlisting>
<![CDATA[
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
);
]]>
</programlisting>
<para>
Для обхода этой проблемы вам необходимо сделать копию
<literal>zend_call_method()</literal> и добавить необходимые параметры.
Вы можете сделать это создав набор макросов
<literal>MY_ZEND_CALL_METHOD_WRAPPER</literal>.
</para>
<para>
<emphasis role="bold">Обращение к пространству пользователя PHP</emphasis>
</para>
<para>
Этот кусок кода демонстрирует оптимизированный метод вызова функций пространства
пользователя из С:
</para>
<programlisting>
<![CDATA[
/* 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)
{
/* Вызов прокси пространства пользователя */
ret = MY_ZEND_CALL_METHOD_WRAPPER(global_user_conn_proxy, host, /*...*/);
}
else
{
/* или оригинальный метод mysqlnd = ничего не делать, быть прозрачным */
ret = org_methods.connect(conn, host, user, passwd,
passwd_len, db, db_len, port,
socket, mysql_flags TSRMLS_CC);
}
return ret;
}
]]>
</programlisting>
<para>
<emphasis role="bold">Обращение к пространству пользователя: простые аргументы
</emphasis>
</para>
<programlisting>
<![CDATA[
/* 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(&zv_host);
/* ... */
}
/* ... */
}
]]>
</programlisting>
<para>
<emphasis role="bold">Обращение к пространству пользователя: структуры как аргументы
</emphasis>
</para>
<programlisting>
<![CDATA[
/* 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(&zv_conn);
/* ... */
}
/* ... */
}
]]>
</programlisting>
<para>
первый аргумент многих методов <literal>mysqlnd</literal> — «объекты» С.
Например, первый аргумент метода connect() — указатель
на <literal>MYSQLND</literal>. Структура MYSQLND —
объект соединения <literal>mysqlnd</literal>.
</para>
<para>
Указатель на объект соединения <literal>mysqlnd</literal> сравнивают со стандартным
обработчиком файлового ввода-вывода. Так же как и он, объект соединения
<literal>mysqlnd</literal> связывают с пространством пользователя
через PHP-тип resource.
</para>
<para>
<emphasis role="bold">Из C в пространство пользователя и обратно</emphasis>
</para>
<programlisting>
<![CDATA[
<?php
class proxy extends mysqlnd_plugin_connection
{
public function connect($conn, $host, ...)
{
/* До внедрения */
printf("Подключение к = '%s'\n", $host);
debug_print_backtrace();
return parent::connect($conn);
}
public function query($conn, $query)
{
/* После внедрения */
$ret = parent::query($conn, $query);
printf("Запрос = '%s'\n", $query);
return $ret;
}
}
mysqlnd_plugin_set_conn_proxy(new proxy());
]]>
</programlisting>
<para>
Пользователи PHP должны иметь возможность вызывать родительские
реализации переопределённых методов.
</para>
<para>
В результате наследования возможно "уточнить" только выбранные
методы и вы можете выбрать, когда выполнять ваш код, до или после родительского.
</para>
<para>
<emphasis role="bold">Встроенный класс:
mysqlnd_plugin_connection::connect() </emphasis>
</para>
<programlisting>
<![CDATA[
/* my_mysqlnd_plugin_classes.c */
PHP_METHOD("mysqlnd_plugin_connection", connect)
{
/* ... упрощённый! ... */
zval *mysqlnd_rsrc;
MYSQLND *conn;
char *host;
int host_len;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs",
&mysqlnd_rsrc, &host, &host_len) == FAILURE)
{
RETURN_NULL();
}
ZEND_FETCH_RESOURCE(conn, MYSQLND * conn, &mysqlnd_rsrc, -1,
"Mysqlnd Connection", le_mysqlnd_plugin_conn);
if (PASS == org_methods.connect(conn, host, /* simplified! */ TSRMLS_CC))
RETVAL_TRUE;
else
RETVAL_FALSE;
}
]]>
</programlisting>
</section>
</chapter><!-- Keep this comment at the end of the file
Local variables:
mode: sgml
sgml-omittag:t
sgml-shorttag:t
sgml-minimize-attributes:nil
sgml-always-quote-attributes:t
sgml-indent-step:1
sgml-indent-data:t
indent-tabs-mode:nil
sgml-parent-document:nil
sgml-default-dtd-file:"~/.phpdoc/manual.ced"
sgml-exposed-tags:nil
sgml-local-catalogs:nil
sgml-local-ecat-files:nil
End:
vim600: syn=xml fen fdm=syntax fdl=2 si
vim: et tw=78 syn=sgml
vi: ts=1 sw=1
-->