При работе с PHP вы можете сами написать расширение PHP. Есть несколько причин, которые я могу придумать, которые побуждают меня сделать это:
- расширить функциональность PHP для определенного использования (математика, статистика, геометрия и т. д.).
- иметь более высокую производительность и эффективность по сравнению с чистой реализацией PHP
- использовать быстроту, полученную при программировании на другом ранее понятном языке (для меня C ++).
Когда дело доходит до выбора инструмента для создания расширений PHP, мы видим два разных подхода:
- Используйте больше про-PHP семантики, например, Zephir .
- Используйте больше семантики pro-C / C ++, например PHP-CPP , о которой пойдет речь в этой статье.
Для меня основной подход к выбору второго подхода прост: я начал заниматься программированием с C / C ++, поэтому я все еще чувствую себя более комфортно при написании этих низкоуровневых модулей на C / C ++. Официальный сайт PHP-CPP дает несколько других причин для этого .
Установка и настройка
PHP-CPP быстро развивается. На момент написания этой статьи она была в версии 0.9.1 (с выпуском 0.9.0 около 2 дней назад). Согласно его документации, «это релиз с ограниченным набором функций, который готовится к следующей версии v1.0», поэтому мы уверены, что скоро выйдет его основной выпуск 1.0.
Таким образом, рекомендуется, по крайней мере, в течение этого промежуточного периода, использовать git
для клонирования репозитория и получить последнее обновление через git pull
.
ПРИМЕЧАНИЕ. В документации по установке PHP-CPP говорится, что в настоящее время он «поддерживает только однопоточные установки PHP», поскольку «внутри движка Zend используется очень странная система для обеспечения безопасности потоков». Будущие выпуски могут поддерживать многопоточные установки PHP, но давайте пока что будем помнить об этом и придерживаться текущих ограничений. К счастью, «однопоточные установки PHP» должны подходить для большинства установок PHP.
PHP-CPP написан на C ++ 11 . Таким образом, более старая версия g ++, установленная в моей Ubuntu 12.04 LTS, не поддерживает ее. Нам нужно обновить наш компилятор g ++ до версии 4.8.x выше. Есть статья, подробно описывающая шаги по обновлению. Пожалуйста, следуйте инструкциям, перечисленным там.
Также при компиляции PHP-CPP будет использоваться заголовочный файл php.h
Этот файл обычно отсутствует в окне Ubuntu, если только не был установлен php-dev
. Мы можем установить связанные с PHP5 файлы разработки, выполнив эту команду:
sudo apt - get install php5 - dev
После обновления g ++ и установки необходимых заголовочных файлов мы можем выполнить следующую команду, чтобы скомпилировать и установить файл библиотеки PHP-CPP ( libphpcpp.so
):
make && sudo make install
Компиляция будет довольно быстрой. После установки файл libphpcpp.so
будет скопирован в /usr/lib
, а все заголовочные файлы PHP-CPP будут скопированы в папки /usr/include
и /usr/include/phpcpp
.
Установка PHP-CPP lib завершена. Это довольно просто, и теперь мы можем перейти к части программирования.
Прежде чем мы это сделаем, мы обсудим несколько важных понятий и терминов, используемых в PHP-CPP. Полную документацию можно найти на ее официальном сайте, и каждый может прочитать ее ДО того, как приступит к реальному программированию.
Скелетные (пустые) файлы проекта расширения
PHP-CPP предоставляет проект расширения скелета , содержащий следующие 3 файла:
-
main.cpp
: основной файл cpp, содержащий функциюget_module
(более подробно будет обсуждаться позже) -
Makefile
: пример файла MAKE для компиляции расширения -
yourextension.ini
: содержит только одну строку для загрузки расширения
Makefile
Если вы знакомы с разработкой * nix, вы знакомы с этим Makefile. Некоторые небольшие изменения должны быть сделаны, чтобы настроить этот файл в соответствии с нашими потребностями:
- Измените
NAME = yourextension
на более значимое, например,NAME = skeleton
. - Измените
INI_DIR = /etc/php5/conf.d
чтобы он соответствовал конфигурации вашей системы. В моем случае этоINI_DIR = /etc/php5/cli/conf.d
. Я изменил путь INI, чтобы сначала включить расширение для клиентской среды PHP.
Это все изменения, которые я сделал. Остальная часть Makefile
может быть сохранена как есть.
yourextension.ini
Я переименовал этот файл в skeleton.ini
и изменил единственную строку в этом файле следующим образом:
extension = skeleton . so
main.cpp
В пустом проекте, предоставленном PHP-CPP, этот файл содержит только одну функцию: get_module()
, которая приведена ниже:
#include <phpcpp.h>
/** * tell the compiler that the get_module is a pure C function */
extern "C" {
/** * Function that is called by PHP right after the PHP process * has started, and that returns an address of an internal PHP * strucure with all the details and features of your extension * * @return void* a pointer to an address that is understood by PHP */ PHPCPP_EXPORT void * get_module ()
{
// static(!) Php::Extension object that should stay in memory
// for the entire duration of the process (that's why it's static)
static Php :: Extension extension ( "yourextension" , "1.0" );
// @todo add your own functions, classes, namespaces to the extension
// return the extension
return extension ;
}
}
А сейчас давайте просто изменим эту строку, чтобы она соответствовала имени расширения, которое мы намереваемся создать:
static Php :: Extension extension ( "skeleton" , "1.0" ); // To be humble, we can change the version number to 0.0.1
get_module()
вызывается PHP, когда последний пытается загрузить нужную библиотеку. Это считается точкой входа для библиотеки. Он объявляется с использованием модификатора extern "C"
, чтобы соответствовать требованию PHP lib для функции get_module()
. Он также использует макрос PHPCPP_EXPORT
который гарантирует, что get_module()
публично экспортируется и, следовательно, вызывается PHP.
До сих пор мы внесли некоторые изменения в пустой проект, чтобы удовлетворить наши потребности. Теперь мы можем скомпилировать и установить этот проект и установить расширение:
make && sudo make install
Далее нам нужно скопировать необходимые файлы в соответствующие папки:
cp - f skeleton . so / usr / lib / php5 / 20121212 cp - f skeleton . ini / etc / php5 / cli / conf . d
Нам просто нужно убедиться, что библиотека skeleton.so
скопирована в нужное место расширений PHP (в моей настройке Ubuntu это должен быть /usr/lib/php5/20121212
как показано выше).
Затем мы можем убедиться, что расширение загружено в CLI с помощью php -i | grep skeleton
php -i | grep skeleton
, и терминал должен отобразить что-то вроде этого:
(Напомним, что skeleton.ini
— это файл, который мы изменили выше, который содержит строку extension=skeleton.so
.)
На данный момент мы скомпилировали и установили наше первое расширение PHP с использованием PHP-CPP. Конечно, это расширение пока ничего не делает. Теперь мы создадим наши первые несколько функций, чтобы лучше понять процесс создания расширений PHP.
Функция «Привет, Тейлор»
Первой функцией, которую мы создадим, будет слегка измененная версия «Hello, World». Давайте сначала посмотрим полный код main.cpp
:
#include <phpcpp.h>
#include <iostream>
void helloWorld ( Php :: Parameters & params )
{ std :: string name = params [ 0 ]; std :: cout << "Hello " << name << "!" << std :: endl ;
}
extern "C" { PHPCPP_EXPORT void * get_module ()
{
static Php :: Extension extension ( "skeleton" , "1.0" ); extension . add ( "helloWorld" , helloWorld );
return extension ;
}
}
Согласно документации PHP-CPP « Зарегистрировать собственные функции », он поддерживает четыре типа сигнатур функций, вызываемых из PHP:
void example1 ();
void example2 ( Php :: Parameters & params );
Php :: Value example3 ();
Php :: Value example4 ( Php :: Parameters & params );
В этом случае я использую вторую сигнатуру, а параметры передаются по значению в виде массива (функция PHP).
Однако в helloWorld
мы специально использовали тип C ++ std::string
для получения первого параметра. Мы также использовали C ++ std
lib для вывода приветственного сообщения.
В функции get_module()
после объявления переменной extension
мы добавляем функцию, которую хотим экспортировать ( helloWorld()
), и присваиваем имя, видимое для скрипта PHP ( helloWorld
).
Теперь давайте скомпилируем и установим расширение. Если все пройдет гладко, новый файл skeleton.so
будет скопирован в каталог расширений.
Мы можем написать простой скрипт для проверки только что созданной функции:
<? php echo "Testing helloWorld in skeleton.so\n" ; echo helloWorld ( 'Taylor' ); echo helloWorld ( 1234 + 5678 ); echo helloWorld ([ 'string' , 123 + 456 ]);
Пожалуйста, найдите время, чтобы посмотреть на результат:
Мы вернемся к тому, что наблюдали здесь позже.
Параметры функции по ссылке
Далее мы увидим другую функцию, которая передает параметры по ссылке, функцию swap()
. В этой функции мы также попытаемся указать количество параметров и их тип.
В main.cpp
мы добавили еще одну функцию swap()
:
void swap ( Php :: Parameters & params ) {
Php :: Value temp = params [ 0 ]; params [ 0 ] = params [ 1 ]; params [ 1 ] = temp ;
}
А также экспортируйте функцию, указав количество параметров и их тип:
extension . add ( "swap" , swap ,{
Php :: ByRef ( "a" , Php :: Type :: Numeric ),
Php :: ByRef ( "b" , Php :: Type :: Numeric )
});
Мы прямо говорим, что:
- Там будет два параметра (
a
иb
); - Они должны быть переданы по ссылке (а не по значению);
- Они должны быть типа Numeric.
Давайте снова скомпилируем и установим обновленное расширение и напишем несколько фрагментов кода, чтобы увидеть, как работают эти новые функции:
<? php $a = 10 ; $b = 20 ;
// swap($a);
// Above will create a segment fault swap ( $a , $b ); echo $a . "|" . $b . "\n" ; $c = 10 ; $d = "string" ; swap ( $c , $d ); echo $c . "|" . $d . "\n" ; $e = 10 ; $f = new \DateTime (); swap ( $e , $f ); var_dump ( $e ); var_dump ( $f );
swap($a)
не удастся. Это ожидаемое и неожиданное. Ожидаемая часть состоит в том, что нам нужны два параметра и только один задан. Но не должна ли эта ошибка быть захвачена PHP при вызове функции swap
и запросе чего-то вроде « Not enough parameters
?
Первый вызов ( swap($a, $b)
) показывает ожидаемый результат: 20|10
. Функция меняет два переданных числа .
Второй вызов почему-то неожиданный: мы сказали PHP, что мы должны поменять местами два числа ! Но он просто игнорирует тот факт, что второй передаваемый параметр является строкой и в любом случае выполняет обмен!
Ну, в некотором смысле, это все еще ожидается. PHP на самом деле не различает тип числа и тип строки. Такое поведение соответствует стандарту PHP. Также из-за этого поведения мы не использовали и не могли использовать внутренние типы C ++ для временной переменной, используемой в функции ( temp
), но использовали Php::Value
в качестве типа переменной.
Третий звонок будет работать. Первый var_dump
покажет объект DateTime, а второй покажет целое число. Это как-то очень неожиданно (по крайней мере для меня). В конце концов, объект сильно отличается от числа / строки. Но если учесть, что такое поведение «подкачки» также возможно в PHP, оно соответствует странным особенностям PHP.
Итак, означает ли это, что спецификация типа не будет иметь никакого влияния? На самом деле, нет. Чтобы уточнить это, мы создадим третью функцию:
void swapObject ( Php :: Parameters & params )
{
Php :: Value temp = params [ 0 ]; params [ 0 ] = params [ 1 ]; params [ 1 ] = temp ;
}
И мы регистрируем эту функцию так:
extension . add ( "swapObject" , swap ,{
Php :: ByRef ( "a" , "sampleClass" ),
Php :: ByRef ( "b" , "sampleClass" )
});
Код тестирования будет выглядеть так:
class sampleClass {
var $i ;
public function __construct ( $n ) { $this -> i = $n ;
}
} $o1 = new sampleClass ( 10 ); $o2 = new sampleClass ( 20 ); swapObject ( $o1 , $o2 ); echo $o1 -> i . "|" . $o2 -> i . "\n" ;
class anotherClass {
} $d1 = new anotherClass (); $d2 = new anotherClass (); swapObject ( $d1 , $d2 );
Первый вызов swapObject()
будет работать, когда мы sampleClass
правильный тип класса ( sampleClass
). Вторая ошибка завершится с сообщением « PHP Catchable fatal error: Argument 1 passed to swapObject() must be an instance of sampleClass, instance of anotherClass given...
».
Приведенный выше фрагмент кода иллюстрирует один важный аспект ограничения типов: объявление скалярных типов в действительности не реализовано. PHP и, следовательно, PHP-CPP обеспечивают только декларацию типа объекта. Кроме того, количество параметров не является обязательным для PHP.
Вывод
В этой статье мы проиллюстрировали шаги по подготовке PHP-CPP к работе в нашей среде PHP. Мы также обсудили некоторые основные шаги по созданию расширения PHP с использованием PHP-CPP (и семантики C ++).
Мы рассмотрели файлы проекта расширения, сигнатуры функций, экспорт / регистрацию функций и типы параметров функций.
В нашей следующей статье мы подробнее рассмотрим несколько ключевых функций PHP-CPP и предоставим реальный пример использования, демонстрирующий использование реализаций класса C ++ и пространства имен с использованием PHP-CPP.