PHP достаточно быстр, но до тех пор, пока не перестаёт им быть. Есть задачи, с которыми не справится никакой PHP: обработка миллионов элементов в циклах, бинарный парсинг, криптография, интеграция с C-библиотеками без PHP-обвязки. Здесь два пути: смириться с узким местом или спуститься на уровень ниже.
PHP-расширения позволяют писать код на C, который выполняется прямо внутри процесса PHP, то есть без подпроцессов, сокетов и сериализации. Ваша C-функция становится полноценной PHP-функцией, вызываемой как любая другая.
Есть и вторая причина для написания расширений, о которой редко говорят: распространение. Если вы создаёте коммерческий PHP-продукт (SDK, проприетарный алгоритм, модуль), доставка скомпилированного файла .so - лучший способ защитить логику. Исходный код остаётся у вас.
В этой статье вы узнаете, как создать настоящее PHP-расширение с нуля и добавить слой проверки лицензии, чтобы оно отказывалось работать без авторизации.

Внутреннее устройство PHP

Движок Zend

PHP не исполняет исходный код напрямую. Сначала он компилирует его в опкоды, а затем виртуальная машина Zend Engine выполняет эти опкоды. При вызове PHP-функции Zend Engine ищет её в глобальной таблице функций и передаёт управление.
Расширения подключаются к этому процессу на уровне C: они регистрируют функции, классы и константы в таблицах Zend Engine при запуске, ещё до выполнения первой строки PHP-скрипта.
Это похоже на систему плагинов, встроенную в сам рантайм. Для движка неважно, определена ли функция на PHP или на C. К моменту вызова она просто запись в хеш-таблице, указывающая на указатель C-функции.

Что такое PHP-расширение?

Расширение - это динамическая библиотека (.so в Linux, .dll в Windows), которую PHP подгружает при старте согласно настройкам php.ini. Оно предоставляет модульную запись - структуру на C, сообщающую PHP имя, версию и хуки жизненного цикла.
Эта структура - контракт между вашим кодом и рантаймом PHP.

Взаимодействие PHP и C

PHP передаёт аргументы в C-функции через абстракцию стека. Возвращаемые значения идут через специальный указатель return_value. Связь между динамической системой типов PHP и статической системой типов C обеспечивает структура zval (об этом ниже).
Zend API предоставляет макросы для всего: парсинга аргументов, возврата значений, генерации исключений, выделения памяти. Вы редко работаете с «сырыми» указателями - макросы всё делают за вас.

Создание базового расширения

Структура файлов

Минимальное расширение требует трёх файлов:
Заголовочный файл
Конфигурация сборки (config.m4)
Основной файл на C (myext.c)

Сборка и установка

Добавьте в php.ini:
Проверьте:

Работа с параметрами и типами данных

Структура zval

Любое значение PHP (строка, число, массив, объект) на уровне C представляется как zval - это tagged union: хранит значение и флаг типа вместе.
Вы редко будете создавать zval вручную, макросы всё сделают за вас.

Парсинг параметров

Макрос ZEND_PARSE_PARAMETERS_START автоматически занимается приведением типов, проверкой количества аргументов и ошибками.

Возврат значений

Пример для строк:
Используйте аллокаторы Zend (emalloc, efree) вместо стандартных malloc/free.

Полезный пример: быстрый подсчёт частот слов

Вызов из PHP:
Всегда используйте estrndup/efree внутри расширений.

Добавление системы лицензирования

Проблема

Вы скомпилировали расширение в .so. Клиент покупает лицензию, разворачивает файл на десяти серверах и вы не контролируете это. Обфускация не спасёт: опытный разработчик быстро разберёт .so. Нужна проверка лицензии при каждом запуске (или периодически), чтобы расширение отказывалось работать без успешной проверки.

Подход

Надёжная система лицензирования обычно включает:
  1. Удалённую проверку через API.
  2. Привязку к домену или IP.
  3. Локальное кэширование результата (с истечением срока).

Реализация проверки лицензии на C

Важно: всегда используйте HTTPS и проверяйте сертификаты (CURLOPT_SSL_VERIFYPEER=1).

Блокировка выполнения при загрузке модуля

Проверку лицензии лучше всего делать в хуке инициализации модуля (PHP_MINIT):
Если проверка не прошла, то функции не регистрируются и расширение не работает.

Настройка php.ini

Пример серверной части (PHP)

Вопросы безопасности

  • Локальная проверка легко обходится: ключ можно найти через strings, objdump.
  • Удалённая проверка надёжнее только если расширение действительно не работает без ответа сервера.
  • Реверс-инжиниринг: даже скомпилированный код можно разобрать инструментами вроде Ghidra или IDA Pro.
  • Не храните ключи в бинарнике; используйте подписи ответов (HMAC/асимметричные ключи).
  • Всегда проверяйте TLS; не доверяйте "сырым" JSON.
  • Базовая обфускация: удалите символы из .so командой strip --strip-all.
  • Для защиты более ценной интеллектуальной собственности рассмотрите обфускаторы типа Obfuscator-LLVM.

Архитектура для продакшна: гибридная модель

Сочетайте три уровня защиты:
  1. PHP-приложение вызывает ваше расширение.
  2. Расширение при инициализации связывается с вашим API для проверки лицензии (и кэширует результат).
  3. Чувствительные операции проксируются через ваш API; логика остаётся на сервере.
  4. Периодическая ревалидация: например, каждые N запросов или M минут.

Заключение: когда стоит использовать этот подход?

Создавайте расширение PHP на C если:
  • Профилирование подтвердило узкое место по производительности.
  • Вы интегрируете библиотеку без PHP-обвязки.
  • Вам нужно распространять коммерческий продукт без раскрытия исходников.
  • Требуется внешняя по отношению к пользовательскому пространству PHP система лицензирования.
Не пишите расширение просто потому что "PHP кажется медленным". Сначала профилируйте. Расширения усложняют сборку и деплой, используйте их только если это действительно нужно.

Финальные советы:

Перед выпуском расширяйте систему лицензирования:
  • Подписывайте ответы сервера (HMAC/асимметричные подписи).
  • Проверяйте срок действия лицензии прямо в C.
  • Добавляйте "окно" для сетевых сбоев (не убивайте прод из-за недоступности сервера лицензий).
  • Ведите аудит обращений к серверу лицензий.
  • Реализуйте механизм быстрой блокировки недобросовестных пользователей без обновления бинарника.
Самое сложное здесь - найти баланс между защитой интеллектуальной собственности и стабильной работой продакшн-систем клиентов. Если сделать всё правильно, то получится решение устойчивое к пиратству и безопасное для бизнеса.