In your dealings with PHP, you may come to consider writing a PHP extension yourself. There are several reasons I can think of that motivate me to do so:
在处理PHP时,您可能会考虑考虑自己编写PHP扩展。 我能想到的几个原因促使我这么做:
- to extend PHP functionality for some very particular usage (mathematics, statistics, geometric, etc). 扩展PHP功能以用于某些非常特殊的用途(数学,统计,几何等)。
- to have a higher performance and efficiency compared to a pure PHP implementation 与纯PHP实现相比具有更高的性能和效率
- to leverage the swiftness obtained from programming in another previously grasped language (for me, C++). 利用从以前掌握的另一种语言(对我而言,C ++)进行编程获得的敏捷性。
When it comes to choosing a tool to build PHP extensions, we see two different approaches:
在选择构建PHP扩展的工具时,我们看到两种不同的方法:
Use more pro-PHP semantics, like Zephir.
使用更多的亲PHP语义,例如Zephir 。
Use more pro-C/C++ semantics, like PHP-CPP, which will be addressed in this article.
使用更多pro-C / C ++语义,例如PHP-CPP ,本文将对此进行介绍。
For me, the main drive to select the second approach is simple: I started my programming hobby with C/C++, so I still feel more comfortable writing those lower level modules in C/C++. PHP-CPP’s official site gives a few other reasons to do so.
对我来说,选择第二种方法的主要动力很简单:我从C / C ++开始了我的编程爱好,因此我仍然更愿意用C / C ++编写那些较低级别的模块。 PHP-CPP的官方网站还提供了其他一些理由 。
安装与配置 (Installation and configuration)
PHP-CPP is evolving rapidly. At the time of this article’s writing, it is in version 0.9.1 (with 0.9.0 released about 2 days before). According to its documentation, “this is a feature-freeze release that prepares for the upcoming v1.0 version”, so we are confident we’ll see its 1.0 major release very soon.
PHP-CPP发展Swift。 在撰写本文时,它的版本为0.9.1(大约2天前发布了0.9.0)。 根据其文档,“这是一个功能冻结的版本,为即将发布的v1.0版本做准备”,因此我们有信心很快就会看到其1.0主要版本。
It is thus recommended, at least during this interim period, to use git
to clone the repository and get the latest update later via git pull
.
因此,建议至少在此过渡期间使用git
克隆存储库,并稍后通过git pull
获得最新更新。
NOTE: The PHP-CPP documentation on installation states that for the time being, it “only supports single-threaded PHP installations” because “internally the Zend engine uses a very odd system to ensure thread safety”. Future releases may support multi-threaded PHP installations but let’s just keep this in mind for now and stick to its current limitation. Luckily, “single-threaded PHP installations” should be the case for most of the PHP installations out there.
注意: 关于安装PHP-CPP 文档指出,由于“ Zend引擎内部使用非常奇怪的系统来确保线程安全”,因此它暂时“仅支持单线程PHP安装”。 未来的版本可能会支持多线程PHP安装,但现在就请记住这一点并坚持其当前限制。 幸运的是,大多数PHP安装都应该采用“单线程PHP安装”。
PHP-CPP is written in C++11. Thus, the older version of g++ installed in my Ubuntu 12.04 LTS does not support it. We need to upgrade our g++ compiler to version 4.8.x above. There is an article detailing the steps to do the upgrading. Please follow the instructions listed there.
PHP-CPP用C ++ 11编写。 因此,我的Ubuntu 12.04 LTS中安装的g ++的较早版本不支持它。 我们需要将g ++编译器升级到上述版本4.8.x。 有一篇文章详细介绍了升级步骤。 请按照此处列出的说明进行操作。
Also, PHP-CPP compilation will use the php.h
header file. This file is normally missing in an Ubuntu box, unless php-dev
was installed. We can install PHP5 related development files by issuing this command:
另外,PHP-CPP编译将使用php.h
头文件。 除非已安装php-dev
,否则通常在Ubuntu框中缺少此文件。 我们可以通过发出以下命令来安装与PHP5相关的开发文件:
sudo apt-get install php5-dev
After upgrading g++ and installing the necessary header files, we can issue the following command to compile and install the PHP-CPP library file (libphpcpp.so
):
升级g ++并安装必要的头文件后,我们可以发出以下命令来编译和安装PHP-CPP库文件( libphpcpp.so
):
make && sudo make install
The compilation will be quite fast. After the installation, the libphpcpp.so
file will be copied over to /usr/lib
, and all PHP-CPP header files will be copied to /usr/include
and /usr/include/phpcpp
folders.
编译将非常快。 安装后, libphpcpp.so
文件将被复制到/usr/lib
,所有PHP-CPP头文件将被复制到/usr/include
和/usr/include/phpcpp
文件夹。
The installation of PHP-CPP lib is now complete. It is quite straightforward and we can now move on to the programming part.
PHP-CPP lib的安装现已完成。 这非常简单,我们现在可以继续进行编程。
Before we do that, we’ll discuss a few important concepts and terminologies used in PHP-CPP. The full documentation can be found on its official site, and everyone is encouraged to read through it BEFORE doing any real programming.
在此之前,我们将讨论PHP-CPP中使用的一些重要概念和术语。 完整的文档可在其官方网站上找到,并鼓励每个人在进行任何实际编程之前通读它。
骨架(空)扩展项目文件 (Skeleton (empty) extension project files)
PHP-CPP provides a skeleton extension project, containing the following 3 files:
PHP-CPP提供了一个框架扩展项目 ,其中包含以下3个文件:
main.cpp
: the main cpp file containing aget_module
function (will be discussed in more detail later)main.cpp
:包含get_module
函数的主cpp文件(稍后将详细讨论)Makefile
: the sample MAKE file to compile the extensionMakefile
:用于编译扩展名的示例MAKE文件yourextension.ini
: contains just one line for extension loadingyourextension.ini
:仅包含一行以加载扩展
生成文件 (Makefile)
If you are familiar with *nix development, you are familiar with this Makefile. Some slight changes shall be made to customize this file to fit our needs:
如果您熟悉* nix开发,则熟悉此Makefile。 为了适应我们的需求,应对文件进行一些细微更改:
Change
NAME = yourextension
to a more meaningful one, likeNAME = skeleton
.将
NAME = yourextension
更改为更有意义的NAME = skeleton
,例如NAME = skeleton
。Change
INI_DIR = /etc/php5/conf.d
to match your system’s configuration. In my case, it isINI_DIR = /etc/php5/cli/conf.d
. I modified the INI path to enable the extension for PHP’s cli environment first.更改
INI_DIR = /etc/php5/conf.d
以匹配您的系统配置。 就我而言,它是INI_DIR = /etc/php5/cli/conf.d
。 我修改了INI路径,以便首先为PHP的cli环境启用扩展。
These are all the changes I have made. The rest of the Makefile
can be kept as it is.
这些都是我所做的更改。 Makefile
的其余部分可以保持原样。
yourextension.ini (yourextension.ini)
I renamed this file to skeleton.ini
and changed the only line in that file like this:
我将此文件重命名为skeleton.ini
并更改了该文件中的唯一一行,如下所示:
extension=skeleton.so
main.cpp (main.cpp)
In the empty project provided by PHP-CPP, this file contains only one function: get_module()
, which is excerpted below:
在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;
}
}
For now, let’s just change this line to match the extension name we intend to create:
现在,让我们更改此行以匹配我们要创建的扩展名:
static Php::Extension extension("skeleton", "1.0"); // To be humble, we can change the version number to 0.0.1
get_module()
is called by PHP when the latter tries to load a required library. It is considered the entry point for a lib. It is declared using the extern "C"
modifier to comply with PHP lib requirement for the get_module()
function. It also uses a macro PHPCPP_EXPORT
which makes sure that get_module()
is publicly exported, and thus callable by PHP.
当PHP尝试加载所需的库时, get_module()
由PHP调用。 它被视为lib的入口点。 使用extern "C"
修饰符声明它,以符合get_module()
函数PHP lib要求。 它还使用一个宏PHPCPP_EXPORT
来确保get_module()
是公开导出的,因此可以被PHP调用。
So far, we have made some changes to the empty project to suit our needs. We can now compile and install this project and install the extension:
到目前为止,我们已经对空项目进行了一些更改以适应我们的需求。 现在,我们可以编译并安装该项目并安装扩展:
make && sudo make install
Next, we need to copy the required files into the appropriate folders:
接下来,我们需要将所需文件复制到适当的文件夹中:
cp -f skeleton.so /usr/lib/php5/20121212
cp -f skeleton.ini /etc/php5/cli/conf.d
We just need to make sure that the skeleton.so
lib is copied to the right location of PHP extensions (in my Ubuntu setup, it should be /usr/lib/php5/20121212
as shown above).
我们只需要确保将skeleton.so
lib复制到PHP扩展的正确位置即可(在我的Ubuntu安装中,它应该是/usr/lib/php5/20121212
如上所示)。
We can then verify the extension is loaded in CLI by php -i | grep skeleton
, and the terminal shall display something like this:
然后,我们可以通过php -i | grep skeleton
验证扩展是否已在CLI中加载。 php -i | grep skeleton
,终端将显示如下内容:
![title alt](https://uploads.sitepoint.com/wp-content/uploads/2014/03/1395454301php-i.png)
(Recall that the skeleton.ini
is the file we modified above, which contains the extension=skeleton.so
line.)
(请记住, skeleton.ini
是我们上面修改的文件,其中包含extension=skeleton.so
行。)
We have so far compiled and installed our first PHP extension using PHP-CPP. Of course, this extension does nothing yet. We will now create our first few functions to further understand the process of building PHP extensions.
到目前为止,我们已经使用PHP-CPP编译并安装了我们的第一个PHP扩展。 当然,此扩展程序什么也不做。 现在,我们将创建我们的前几个功能,以进一步了解构建PHP扩展的过程。
“你好,泰勒”功能 (“Hello, Taylor” function)
The first function we create will be a slightly modified version of “Hello, World”. Let’s see the full code of main.cpp
first:
我们创建的第一个功能将是“ Hello,World”的略微修改版本。 首先让我们看一下main.cpp
的完整代码:
#include <phpcpp.h>
#include <iostream>
void helloWorld (Php::Parameters ¶ms)
{
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;
}
}
According to the PHP-CPP documentation on “Register native functions“, it supports four types of function signatures to be called from PHP:
根据有关“ 注册本机函数 ”PHP-CPP文档,它支持从PHP调用的四种类型的函数签名:
void example1();
void example2(Php::Parameters ¶ms);
Php::Value example3();
Php::Value example4(Php::Parameters ¶ms);
In this case, I am using the second signature and the parameters are passed by value in an array form (PHP feature).
在这种情况下,我正在使用第二个签名,并且参数以数组形式(PHP功能)按值传递。
However, in helloWorld
, we have specifically used C++ type std::string
to grab the first parameter. We have also used C++ std
lib to output a welcoming message.
但是,在helloWorld
,我们专门使用了C ++类型std::string
来获取第一个参数。 我们还使用了C ++ std
lib输出欢迎消息。
In get_module()
function, after declaring the extension
variable, we add the function we would like to export (helloWorld()
) and assign a name visible to the PHP script (helloWorld
).
在get_module()
函数中,声明extension
变量后,我们添加要导出的函数( helloWorld()
),并为PHP脚本分配一个可见的名称( helloWorld
)。
Now let’s compile and install the extension. If everything goes smoothly, the new skeleton.so
file will be copied to the extension directory.
现在,我们编译并安装扩展。 如果一切顺利,新的skeleton.so
文件将被复制到扩展目录。
We can write a simple script to test the function just created:
我们可以编写一个简单的脚本来测试刚刚创建的功能:
<?php
echo "Testing helloWorld in skeleton.so\n";
echo helloWorld('Taylor');
echo helloWorld(1234+5678);
echo helloWorld(['string', 123+456]);
Please take some time to look at the output:
请花一些时间查看输出:
![title alt](https://uploads.sitepoint.com/wp-content/uploads/2014/03/1395454298helloworld.png)
We will come back to what we have observed here later.
稍后我们将返回到此处已观察到的内容。
功能参数参考 (Function parameters by reference)
Next, we will see another function which passes parameters by reference, a swap()
function. In this function, we will also try to specify the number of parameters and their type.
接下来,我们将看到另一个通过引用传递参数的函数, swap()
函数。 在此功能中,我们还将尝试指定参数的数量及其类型。
In main.cpp
, we add one more function swap()
:
在main.cpp
,我们再添加一个功能swap()
:
void swap(Php::Parameters ¶ms) {
Php::Value temp = params[0];
params[0] = params[1];
params[1] = temp;
}
And also export the function by specifying the number of parameters and their type:
并且还通过指定参数数量及其类型来导出函数:
extension.add("swap", swap,{
Php::ByRef("a", Php::Type::Numeric),
Php::ByRef("b", Php::Type::Numeric)
});
We explicitly say that:
我们明确地说:
There will be two parameters (
a
andb
);将有两个参数(
a
和b
);- They should be passed by reference (instead of by value); 它们应该通过引用传递(而不是通过值传递);
- They should be of type Numeric. 它们应为数字类型。
Let’s compile and install the updated extension again and write some code snippets to see how this new functions works:
让我们再次编译并安装更新的扩展,并编写一些代码片段以查看此新功能如何工作:
<?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)
will fail. This is expected and unexpected. The expected part is that we need two parameters and only one is given. But, shouldn’t that error be captured by PHP when calling the function swap
and prompting us something like Not enough parameters
?
swap($a)
将失败。 这是意料之外的。 预期的部分是我们需要两个参数,并且仅给出一个。 但是,调用函数swap
并提示诸如Not enough parameters
类的内容时,PHP是否应该捕获该错误?
The first call (swap($a, $b)
) shows the expected result: 20|10
. The function swaps the two numbers passed in.
第一次调用( swap($a, $b)
)显示预期结果: 20|10
。 该函数交换传入的两个数字 。
The second call is somehow unexpected: we have told PHP that we are to swap two numbers! But it just ignores the fact that the 2nd parameter passed is a string and does the swapping anyway!
第二个调用某种程度上是出乎意料的:我们已经告诉PHP我们要交换两个数字 ! 但是它只是忽略了以下事实:传递的第二个参数是一个字符串,无论如何都进行交换!
Well, in a way, it is still expected. PHP does not really distinguish a number type and a string type. This behavior complies to the PHP standard. Also due to this behavior, we didn’t and can’t use C++ internal types for the temporary variable used in the function (temp
) but used Php::Value
as the variable type.
好吧,从某种意义上说,这仍然是预期的。 PHP并没有真正区分数字类型和字符串类型。 此行为符合PHP标准。 同样由于这种行为,我们没有也不能使用C ++内部类型作为函数( temp
)中使用的临时变量,而是使用Php::Value
作为变量类型。
The third call will work. The first var_dump
will show the DateTime object and the second will show the integer. This is somehow very much unexpected (at least to me). After all, an object is quite different from a number/string. But after considering that this “swap” behavior is also doable in PHP, it fits in with PHP’s oddities.
第三次通话将起作用。 第一个var_dump
将显示DateTime对象,第二个将显示整数。 这在某种程度上是非常出乎意料的(至少对我而言)。 毕竟,对象与数字/字符串完全不同。 但是,在考虑到这种“交换”行为在PHP中也是可行的之后,它就适合PHP的怪癖。
So, does it mean the “type” specification won’t have any impact? Not really. To further elaborate this, we create a third function:
那么,这是否意味着“类型”规范不会产生任何影响? 并不是的。 为了进一步阐述这一点,我们创建了第三个函数:
void swapObject(Php::Parameters ¶ms)
{
Php::Value temp = params[0];
params[0] = params[1];
params[1] = temp;
}
And we register this function like this:
我们像下面这样注册此功能:
extension.add("swapObject", swap,{
Php::ByRef("a", "sampleClass"),
Php::ByRef("b", "sampleClass")
});
The testing code will be like this:
测试代码将如下所示:
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);
The first call to swapObject()
will work as we passed in the correct class type (sampleClass
). The second will fail, displaying “PHP Catchable fatal error: Argument 1 passed to swapObject() must be an instance of sampleClass, instance of anotherClass given...
“.
当我们传入正确的类类型( sampleClass
)时,对swapObject()
的第一次调用将起作用。 第二个将失败,并显示“ PHP Catchable fatal error: Argument 1 passed to swapObject() must be an instance of sampleClass, instance of anotherClass given...
”。
The above code segment illustrates one important aspect on type restriction: scalar types declaration is not really implemented. PHP and thus PHP-CPP only enforce object-type declaration. Also, the number of parameters is not really enforced on the PHP side.
上面的代码段说明了类型限制的一个重要方面:标量类型声明并未真正实现。 PHP和PHP-CPP仅强制执行对象类型声明。 另外,PHP端并没有真正强制执行参数的数量。
结论 (Conclusion)
In this article, we illustrated the steps to prepare PHP-CPP to work for our PHP environment. We also discussed some basic steps to create a PHP extension using PHP-CPP (and C++ semantics).
在本文中,我们说明了准备PHP-CPP以使其适用于我们PHP环境的步骤。 我们还讨论了使用PHP-CPP(和C ++语义)创建PHP扩展的一些基本步骤。
We covered the extension project files, function signatures, function export/registration, and the function parameter types.
我们介绍了扩展项目文件,功能签名,功能导出/注册和功能参数类型。
In our next article, we will further elaborate a few key features in PHP-CPP and provide a real-world use case demonstrating the usage of C++ class and namespace implementations using PHP-CPP.
在我们的下一篇文章中,我们将进一步阐述PHP-CPP的一些关键功能,并提供一个真实的用例,以演示使用PHP-CPP的C ++类和名称空间实现的用法。
翻译自: https://www.sitepoint.com/getting-started-php-extension-development-via-php-cpp/