phper如何用Rust開發PHP擴充套件Liunx版【詳細教程】

語言: CN / TW / HK

點選進入“PHP開源社群”

免費獲取進階面試、文件、視訊資源

一、前言

眾所周知,作為一名phper,對php有些功能的缺失有時會感到尷尬,比如:呼叫ffmpeg視訊處理工具。沒有專門的擴充套件來操作的,什麼?利用php system函式呼叫?對於開源的php指令碼,這太不安全了!這個時候作為深資的phper會考慮開發php擴充套件,在擴充套件中實現對ffmpeg的操作。

為什麼要使用php擴充套件?

優點:

  • php擴充套件是C開發的,那速度沒得說。

  • 耦合性高,它的出現就是用來增強php的。

  • 安全性高,畢竟擴充套件是編譯後的程式,程式碼不開源。

缺點:

  • 需針對php版本及系統環境進行開發,那麼就比較麻煩了。也就是說7.4版本的php,liunx環境下開發的擴充套件,只支援該php版本及系統。

  • 需要會C、C++,當然本文是以rust進行開發,對C的資料型別進行了解,對rust FFI的操作及資料型別轉換需精通。

  • 除錯相對麻煩。

為什麼要用rust開發php擴充套件?

原因很簡單,這還要說起rust的語言特性

因“所有權”的特性使你的程式更安全,不會像C那樣出現各種“玄學BUG”。擁有C一樣的效能,我還是很看好它的發展。

rust開發php擴充套件流程:

當然,rust目前是沒有專門開發php擴充套件的骨架。所以我的邏輯也很簡單,利用rust開發靜態庫暴露給C【涉及FFI的瞭解】。我們在php官方骨架中直接引入rust靜態庫呼叫其方法即可。

二、開發環境與流程

開發環境

寶塔【CentOS 7.6】、GCC【涉及php擴充套件骨架的編譯,我這裡系統內建就有,如果編譯擴充套件報相關錯了自行安裝】

開發整體流程:

1、準備寶塔,安裝這裡就不說了

這裡我們以開發php7.4擴充套件為例。

2、下載php7.4 liunx版原始碼

php官網: https://www.php.net/

注意!該原始碼版本必須與你環境php版本完全一致!!!

3、上傳php原始碼到寶塔

/usr/phper

在usr下建立一個phper資料夾,然後將原始碼壓縮包上傳到此處。

解壓該壓縮包

4、建立一個我們自己的擴充套件

/usr/phper/php-7.4.30/ext目錄下有這麼一個php檔案,它可以建立擴充套件!

注意設定命令列版本,因為接下來利用php命令必須是版本一致的!

在剛剛的目錄下,點選終端,輸入建立擴充套件命令。

php ext_skel.php --ext 副檔名稱

這裡就多出了一個新的擴充套件原始碼檔案。

在該目錄下點選終端,輸入:

phpize

接著輸入:

./configure --with-php-config=/www/server/php/74/bin/php-config

注意這個引數php路徑,如果是別的版本,請自行在寶塔裡安裝找到對應版本路徑,它們都是放一起的。

回車開始進行檢查了

最後輸入:

make

進行編譯。

這個目錄下便是編譯出來的so擴充套件最終檔案了!

讓我們看下預設生成的擴充套件有哪些功能

檢視主檔案【需瞭解php擴充套件骨架,這裡以它預設給的為例。

也就是說,剛剛編譯出來的擴充套件,是有這兩個函式的,咱們測試一下玩玩。

注意!每次修改主檔案,都需要重新按上述命令跑一遍,否則不生效,很奇怪!

phpize
./configure --with-php-config=/www/server/php/74/bin/php-config
make

5、使用擴充套件

複製剛剛生成的擴充套件檔案到我們php環境的擴充套件裡

配置php.ini載入hello.so擴充套件

extension = hello.so

儲存後記得重新啟動下php,否則不生效的!

在檔案管理中點選終端,輸入:

php -m

可以看到我們的擴充套件在列表中了。

建立一個站點,測試下擴充套件中兩個函式。

看好,php版本是7.4

訪問站點,發現沒有問題。

當然也可以通過命令列執行php指令碼檢視結果【前提是網站那裡php命令列版本設定的7.4】

php index.php

OK!從建立到生成到使用擴充套件的流程結束,接下來才進入正題,開始用rust開發擴充套件。

6、rust與php擴充套件的整合開發

開發工具:CLion

需要rust環境與CLion中rust外掛的安裝與配置,這個自行去百度,比我想象中的全!

建立一個hello命名的庫專案

我們寫兩個匯出函式,分別是加法功能和base64字串解析功能。

lib.rs

#![crate_type = "staticlib"]



extern crate libc;



//使用C型別約束
use std::ffi::{CStr, CString};
use libc::{c_char, c_int};



//add_int【引數:兩個c語言的int型別】:對兩個int型別數值進行相加
#[no_mangle]
pub extern "C" fn add_int(x:c_int, y:c_int) -> c_int{
//兩個數相加
return x + y;
}



//base64_decode函式【引數:c語言的*char型別】:對字串進行base64解碼
#[no_mangle]
pub extern "C" fn base64_decode(s:*const c_char) -> *mut c_char {
//c char型別轉&str
let h = unsafe{CStr::from_ptr(s).to_str().unwrap()};
//base64 解碼
let s = base64::decode(h.to_string());
if let Err(_s) = s {
panic!("型別錯誤!");
}
let n = String::from_utf8(s.unwrap().clone()).unwrap();
//String 轉 C CString
let a = CString::new(n.as_str()).unwrap();
//C CString 轉 C char
//這裡實屬無奈,因為rust ffi中闡述,對字串返回只能是該字串地址,所以需要該方法進行返回C才能接收到!
let r = a.into_raw();
return r;
}

Cargo.toml

[package]

name = "hello"

version = "0.1.0"

edition = "2021"



# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lib]

name = "hello"

crate-type = ["staticlib"]



[dependencies]

libc = "*"

base64 = "0.12.1"

注意在編譯過程中涉及系統型別,不然在引入該靜態庫編譯擴充套件可能報錯,提示不支援。

編譯64位靜態庫

rustup target add x86_64-unknown-linux-musl

cargo build --target x86_64-unknown-linux-musl --release

編譯32位靜態庫

rustup target add i686-unknown-linux-musl

cargo build --target i686-unknown-linux-musl --release

這裡我們是64位系統。

會生成一個.a檔案,該檔案便是liunx支援的靜態庫檔案。

生成支援C語言的膠水標頭檔案【用於C呼叫該庫需要寫的函式宣告,很方便】

建立 cbindgen.toml 檔案

內容:

language = "C"

安裝cbindgen,建立標頭檔案。

cargo install --force cbindgen
cbindgen --config cbindgen.toml --crate 專案名稱 --output 標頭檔案名稱.h

自動生成了C語言的函式宣告hello.h檔案,用於呼叫。

回到之前我們建立的hello擴充套件

建立lib資料夾

將剛剛編譯出來的靜態庫.a檔案上傳到lib目錄下

將剛剛建立的.h標頭檔案上傳到擴充套件目錄下

配置.m4預編譯檔案【關鍵】

設定引入lib資料夾中的靜態庫檔案

  PHP_ADD_LIBRARY_WITH_PATH(hello, /usr/phper/php-7.4.30/ext/hello/lib, HELLO_SHARED_LIBADD)
PHP_SUBST(HELLO_SHARED_LIBADD)

儲存.m4

編寫主檔案

/* hello extension for PHP */



#ifdef HAVE_CONFIG_H
# include "config.h"
#endif



#include "php.h"
#include "ext/standard/info.h"
#include "php_hello.h"
#include "hello.h"//引入標頭檔案
/* For compatibility with older PHP versions */
#ifndef ZEND_PARSE_PARAMETERS_NONE
#define ZEND_PARSE_PARAMETERS_NONE() \
ZEND_PARSE_PARAMETERS_START(0, 0) \
ZEND_PARSE_PARAMETERS_END()
#endif



/* {{{ void hello_test1()
*/
PHP_FUNCTION(hello_test1)
{
ZEND_PARSE_PARAMETERS_NONE();



int num = add_int(1,2);//rust中兩個數相加函式並返回。



php_printf("The extension %d is loaded and working!\r\n", num);
}
/* }}} */



/* {{{ string hello_test2( [ string $var ] )
*/
PHP_FUNCTION(hello_test2)
{
char *var = "World";
size_t var_len = sizeof("World") - 1;
zend_string *retval;



ZEND_PARSE_PARAMETERS_START(0, 1)
Z_PARAM_OPTIONAL
Z_PARAM_STRING(var, var_len)
ZEND_PARSE_PARAMETERS_END();



char *newstr = base64_decode(var);//rust中解析base64字串並返回。



retval = strpprintf(0, "Hello %s", newstr);



RETURN_STR(retval);
}
/* }}}*/



/* {{{ PHP_RINIT_FUNCTION
*/
PHP_RINIT_FUNCTION(hello)
{
#if defined(ZTS) && defined(COMPILE_DL_HELLO)
ZEND_TSRMLS_CACHE_UPDATE();
#endif



return SUCCESS;
}
/* }}} */



/* {{{ PHP_MINFO_FUNCTION
*/
PHP_MINFO_FUNCTION(hello)
{
php_info_print_table_start();
php_info_print_table_header(2, "hello support", "enabled");
php_info_print_table_end();
}
/* }}} */



/* {{{ arginfo
*/
ZEND_BEGIN_ARG_INFO(arginfo_hello_test1, 0)
ZEND_END_ARG_INFO()



ZEND_BEGIN_ARG_INFO(arginfo_hello_test2, 0)
ZEND_ARG_INFO(0, str)
ZEND_END_ARG_INFO()
/* }}} */



/* {{{ hello_functions[]
*/
static const zend_function_entry hello_functions[] = {
PHP_FE(hello_test1, arginfo_hello_test1)
PHP_FE(hello_test2, arginfo_hello_test2)
PHP_FE_END
};
/* }}} */



/* {{{ hello_module_entry
*/
zend_module_entry hello_module_entry = {
STANDARD_MODULE_HEADER,
"hello", /* Extension name */
hello_functions, /* zend_function_entry */
NULL, /* PHP_MINIT - Module initialization */
NULL, /* PHP_MSHUTDOWN - Module shutdown */
PHP_RINIT(hello), /* PHP_RINIT - Request initialization */
NULL, /* PHP_RSHUTDOWN - Request shutdown */
PHP_MINFO(hello), /* PHP_MINFO - Module info */
PHP_HELLO_VERSION, /* Version */
STANDARD_MODULE_PROPERTIES
};
/* }}} */



#ifdef COMPILE_DL_HELLO
# ifdef ZTS
ZEND_TSRMLS_CACHE_DEFINE()
# endif
ZEND_GET_MODULE(hello)
#endif

刪除之前生成的擴充套件檔案

重新生成擴充套件

phpize

./configure --with-php-config=/www/server/php/74/bin/php-config

make

大小都變了,說明我們的靜態庫在裡面了哈哈。

按上述使用擴充套件流程替換擴充套件

注意!替換擴充套件檔案後要重啟PHP哦,不然不生效!

7、測試rust開發的php擴充套件

網頁測試和命令列測試

也可以通過php擴充套件骨架直接進行測試

編寫要執行測試的擴充套件函式

--TEST--

hello_test2() Basic test

--SKIPIF--

<?php

if (!extension_loaded('hello')) {

echo 'skip';

}

?>

--FILE--

<?php

hello_test1();

var_dump(hello_test2('5LiA56CB6LaF5Lq6'));

?>

--EXPECT--

string(11) "Hello World"

string(9) "Hello PHP"

擴充套件目錄下直接輸入:

make test

執行後 tests目錄下輸出了一個.out檔案

不是這樣更方便了呢?

如果你年滿18週歲以上,又覺得學【PHP】太難?想嘗試其他程式語言,那麼我推薦你學Python,現有價值499元Python零基礎課程限時免費領取,限10個名額!

掃描二維碼-免費領取

點選“檢視原文”獲取更多