PHP-CPP是一個用於開發PHP擴展的C++庫。本節講解如何在C++中實現PHP類。
類和對象
類和對象
怎樣在PHP-CPP里寫出PHP的類呢?很簡單,看下面的例子:
main.cpp
/**
* User: 公眾號: 飛鴻影的博客(fhyblog)
* Date: 2018/7
*/
#include <time.h>
#include <phpcpp.h>
/**
* Counter class that can be used for counting
*/
class Counter : public Php::Base
{
private:
/**
* The initial value
* @var int
*/
int _value = 0;
public:
/**
* C++ constructor and destructor
*/
Counter() = default;
virtual ~Counter() = default;
/**
* Update methods to increment or decrement the counter
* Both methods return the NEW value of the counter
* @return int
*/
Php::Value increment() { return ++_value; }
Php::Value decrement() { return --_value; }
/**
* Method to retrieve the current counter value
* @return int
*/
Php::Value value() const { return _value; }
//類的靜態成員函數
static Php::Value gettime() {return time(NULL);}
};
/**
* Switch to C context to ensure that the get_module() function
* is callable by C programs (which the Zend engine is)
*/
extern "C" {
/**
* Startup function that is called by the Zend engine
* to retrieve all information about the extension
* @return void*
*/
PHPCPP_EXPORT void *get_module() {
// 必須是static類型,因為擴展對象需要在PHP進程內常駐內存
static Php::Extension extension("helloworld", "1.0.0");
//初始化導出類
Php::Class<Counter> counter("Counter");
//注冊導出類的可訪問普通函數
counter.method<&Counter::increment> ("increment");
counter.method<&Counter::decrement> ("decrement");
counter.method<&Counter::value> ("value");
//注冊導出類的可訪問靜態函數
counter.method<&Counter::gettime>("gettime");
//注冊導出類,使用右值引用方式,優化資源使用
extension.add(std::move(counter));
// 返回擴展對象指針
return extension;
}
}
首先,C++類必須繼承自Php::Base
;其次,當我們將類添加到擴展對象時,還必須指定要從PHP訪問的所有方法;最后再注冊導出類。
我們先測試:
/**
* User: 公眾號: 飛鴻影的博客(fhyblog)
* Date: 2018/7
*/
$counter = new Counter;
echo 'result of increment() = '. $counter->increment() . PHP_EOL;
echo 'result of increment() = '. $counter->increment() . PHP_EOL;
echo 'result of decrement() = '. $counter->decrement() . PHP_EOL;
echo 'result of value() = '. $counter->value() . PHP_EOL;
echo 'result of gettime() = '. Counter::gettime() . PHP_EOL;
輸出:
result of increment() = 1
result of increment() = 2
result of decrement() = 1
result of value() = 1
result of gettime() = 1531621728
訪問修飾符
我們還可以對導出的方法添加訪問修飾符:
//初始化導出類
Php::Class<Counter> counter("Counter");
//注冊導出類的可訪問普通函數
counter.method<&Counter::increment> ("increment", Php::Private, {
Php::ByVal("a", Php::Type::Numeric)
});
counter.method<&Counter::decrement> ("decrement", Php::Protected, {
Php::ByVal("a", Php::Type::Numeric)
});
counter.method<&Counter::value> ("value");
Php::Class::method
第二個參數支持設置訪問修飾符,默認是public;第三個參數和普通函數一樣,支持設置參數類型。
支持的訪問修飾符:
extern PHPCPP_EXPORT const int Static;
extern PHPCPP_EXPORT const int Abstract;
extern PHPCPP_EXPORT const int Final;
extern PHPCPP_EXPORT const int Public;
extern PHPCPP_EXPORT const int Protected;
extern PHPCPP_EXPORT const int Private;
extern PHPCPP_EXPORT const int Const;
有一點需要注意:C++里要導出的方法,必須全是Public的, 即使我們在PHP中將它們標記為私有或受保護。因為我們寫的方法由PHP-CPP庫調用,如果將它們設為私有,它們將對庫不可見。
抽象類、Final類
聲明類為Final很簡單,只需要在初始化導出類的時候聲明一下即可:
Php::Class<Counter> counter("Counter", Php::Final);
那么怎么聲明一個抽象類呢?上面的例子里Php::Class::method
都傳入了真正的C ++方法的地址,但是抽象方法通常沒有實現,那么我們需要怎么提供指向方法的指針?幸運的是,在PHP-CPP里注冊抽象方法不用提供指向C ++方法的指針。
示例:
抽象類原申明:
/**
* User: 公眾號: 飛鴻影的博客(fhyblog)
* Date: 2018/7
*/
#include <phpcpp.h>
//類聲明
class MyAbstract : public Php::Base{};
extern "C" {
PHPCPP_EXPORT void *get_module()
{
// 必須是static類型,因為擴展對象需要在PHP進程內常駐內存
static Php::Extension extension("helloworld", "1.0.0");
//初始化導出類
Php::Class<MyAbstract> my_abstract("MyAbstract", Php::Abstract);
//注冊抽象方法:如果不給出C++方法的地址,該方法自動變成抽象方法
my_abstract.method("myAbstractMethod", {
Php::ByVal("a", Php::Type::String, true)
});
extension.add(std::move(my_abstract));
// 返回擴展對象指針
return extension;
}
}
我們在test.php嘗試去實例化MyAbstract
類,提示:
PHP Fatal error: Uncaught Error: Cannot instantiate abstract class MyAbstract
注:官方示例里初始化導出類里沒有加
Php::Abstract
,測試的時候發現還是可以實例化的,只是調用抽象方法才報錯。
構造函數和析構函數
在C++代碼里,PHP的構造函數和析構函數本質上是普通方法。明白了這點,就不難實現了。
示例:
/**
* User: 公眾號: 飛鴻影的博客(fhyblog)
* Date: 2018/7
*/
#include <phpcpp.h>
/**
* Simple counter class
*/
class Counter : public Php::Base
{
private:
/**
* Internal value
* @var int
*/
int _value = 0;
public:
/**
* c++ constructor
*/
Counter() = default;
/**
* c++ destructor
*/
virtual ~Counter() = default;
/**
* php "constructor"
* @param params
*/
void __construct(Php::Parameters ¶ms)
{
// copy first parameter (if available)
if (!params.empty()) _value = params[0];
}
/**
* functions to increment and decrement
*/
Php::Value increment() { return ++_value; }
Php::Value decrement() { return --_value; }
Php::Value value() const { return _value; }
};
/**
* Switch to C context so that the get_module() function can be
* called by C programs (which the Zend engine is)
*/
extern "C" {
/**
* Startup function for the extension
* @return void*
*/
PHPCPP_EXPORT void *get_module() {
static Php::Extension myExtension("my_extension", "1.0");
// description of the class so that PHP knows which methods are accessible
Php::Class<Counter> counter("Counter");
counter.method<&Counter::__construct>("__construct");
counter.method<&Counter::increment>("increment");
counter.method<&Counter::decrement>("decrement");
counter.method<&Counter::value>("value");
// add the class to the extension
myExtension.add(std::move(counter));
// return the extension
return myExtension;
}
}
如果需要構造函數為私有的,只需要在注冊的時候加個flag:
counter.method<&Counter::__construct>("__construct", Php::Private);
如果要禁止被clone,可以:
// alternative way to make an object unclonable
counter.method("__clone", Php::Private);
接口
接口(Interface)由於不需要具體方法的實現,我們可以通過與定義類的方式類似的方式來實現。唯一的區別是我們不使用Php::Class<YourClass>
,而是一個Php::Interface
實例。
//初始化
Php::Interface interface("MyInterface");
//添加成員方法
interface.method("myMethod", {
Php::ByVal("value", Php::Type::String, true)
});
//注冊到擴展
extension.add(std::move(interface));
繼承
implement 實現
我們除了可以在PHP代碼去實現接口或者繼承類,也可以在C++里實現。該Php::Class<YourClass>
對象有extends()
和implements()
,可用於指定基類和實現的接口。我們需要傳入之前配置的類或接口。我們來看一個例子。
/**
* User: 公眾號: 飛鴻影的博客(fhyblog)
* Date: 2018/7
*/
#include <phpcpp.h>
#include <iostream>
class MyClass : public Php::Base
{
public:
Php::Value myMethod(Php::Parameters ¶ms){
Php::out << "MyClass" << std::endl;
return params;
}
};
extern "C" {
PHPCPP_EXPORT void *get_module()
{
static Php::Extension extension("helloworld", "1.0.0");
//定義接口
Php::Interface interface("MyInterface");
interface.method("myMethod", {
Php::ByVal("value", Php::Type::String, true)
});
extension.add(std::move(interface));
// 注冊一個自定義類
Php::Class<MyClass> myClass("MyClass");
// 實現接口定義
myClass.implements(interface);
myClass.method<&MyClass::myMethod>("myMethod", {
Php::ByVal("value", Php::Type::String, true)
});
extension.add(std::move(myClass));
// 返回擴展對象指針
return extension;
}
}
測試:
$obj = new MyClass();
var_dump($obj->myMethod(11));
extends 繼承
PHP的繼承與C++的繼承沒有直接關系,必須顯示使用Php::Class::extends()
進行繼承。
還是接着上面的例子說明。
/**
* User: 公眾號: 飛鴻影的博客(fhyblog)
* Date: 2018/7
*/
#include <phpcpp.h>
#include <iostream>
class MyClass : public Php::Base
{
public:
Php::Value myMethod(Php::Parameters ¶ms){
Php::out << "MyClass" << std::endl;
return params;
}
};
class MySubClass : public Php::Base{
};
extern "C" {
PHPCPP_EXPORT void *get_module()
{
static Php::Extension extension("helloworld", "1.0.0");
//定義接口
Php::Interface interface("MyInterface");
interface.method("myMethod", {
Php::ByVal("value", Php::Type::String, true)
});
// 注冊一個自定義類
Php::Class<MyClass> myClass("MyClass");
// 實現接口定義
myClass.implements(interface);
myClass.method<&MyClass::myMethod>("myMethod", {
Php::ByVal("value", Php::Type::String, true)
});
Php::Class<MySubClass> mySubClass("MySubClass");
mySubClass.extends(myClass);
extension.add(std::move(interface));
extension.add(std::move(mySubClass));
extension.add(std::move(myClass));
// 返回擴展對象指針
return extension;
}
}
注:注冊類(
extension.add
)需要放到extends方法的后面,也就是不能先注冊父類再使用extends,否則無法繼承。建議實際編程的時候注冊統一放到最后面。
魔術方法
在PHP-CPP里,僅__construct()
需要顯示的在get_module()
里注冊,其他的魔術方法像__get()
、__set()
、__call()
、__toString()
等都不需要注冊。
#include <phpcpp.h>
/**
* A sample class, that has some pseudo properties that map to native types
*/
class User : public Php::Base
{
private:
/**
* Name of the user
* @var std::string
*/
std::string _name;
/**
* Email address of the user
* @var std::string
*/
std::string _email;
public:
/**
* C++ constructor and C++ destructpr
*/
User() = default;
virtual ~User() = default;
/**
* Get access to a property
* @param name Name of the property
* @return Value Property value
*/
Php::Value __get(const Php::Value &name)
{
// check if the property name is supported
if (name == "name") return _name;
if (name == "email") return _email;
// property not supported, fall back on default
return Php::Base::__get(name);
}
/**
* Overwrite a property
* @param name Name of the property
* @param value New property value
*/
void __set(const Php::Value &name, const Php::Value &value)
{
// check the property name
if (name == "name")
{
// store member
_name = value.stringValue();
}
// we check emails for validity
else if (name == "email")
{
// store the email in a string
std::string email = value;
// must have a '@' character in it
if (email.find('@') == std::string::npos)
{
// email address is invalid, throw exception
throw Php::Exception("Invalid email address");
}
// store the member
_email = email;
}
// other properties fall back to default
else
{
// call default
Php::Base::__set(name, value);
}
}
/**
* Check if a property is set
* @param name Name of the property
* @return bool
*/
bool __isset(const Php::Value &name)
{
// true for name and email address
if (name == "name" || name == "email") return true;
// fallback to default
return Php::Base::__isset(name);
}
/**
* Remove a property
* @param name Name of the property to remove
*/
void __unset(const Php::Value &name)
{
// name and email can not be unset
if (name == "name" || name == "email")
{
// warn the user with an exception that this is impossible
throw Php::Exception("Name and email address can not be removed");
}
// fallback to default
Php::Base::__unset(name);
}
/**
* Overriden __call() method to accept all method calls
* @param name Name of the method that is called
* @param params Parameters that were passed to the method
* @return Value The return value
*/
Php::Value __call(const char *name, Php::Parameters ¶ms)
{
// the return value
std::string retval = std::string("__call ") + name;
// loop through the parameters
for (auto ¶m : params)
{
// append parameter string value to return value
retval += " " + param.stringValue();
}
// done
return retval;
}
/**
* Overriden __callStatic() method to accept all static method calls
* @param name Name of the method that is called
* @param params Parameters that were passed to the method
* @return Value The return value
*/
static Php::Value __callStatic(const char *name, Php::Parameters ¶ms)
{
// the return value
std::string retval = std::string("__callStatic ") + name;
// loop through the parameters
for (auto ¶m : params)
{
// append parameter string value to return value
retval += " " + param.stringValue();
}
// done
return retval;
}
/**
* Overridden __invoke() method so that objects can be called directly
* @param params Parameters that were passed to the method
* @return Value The return value
*/
Php::Value __invoke(Php::Parameters ¶ms)
{
// the return value
std::string retval = "invoke";
// loop through the parameters
for (auto ¶m : params)
{
// append parameter string value to return value
retval += " " + param.stringValue();
}
// done
return retval;
}
/**
* Cast to a string
* @return Value
*/
Php::Value __toString()
{
return "abcd";
}
};
/**
* Switch to C context to ensure that the get_module() function
* is callable by C programs (which the Zend engine is)
*/
extern "C" {
/**
* Startup function that is called by the Zend engine
* to retrieve all information about the extension
* @return void*
*/
PHPCPP_EXPORT void *get_module() {
// extension object
static Php::Extension myExtension("my_extension", "1.0");
// description of the class so that PHP knows
// which methods are accessible
Php::Class<User> user("User");
// add the class to the extension
myExtension.add(std::move(user));
// return the extension
return myExtension;
}
}
測試:
<?php
// initialize user and set its name and email address
$user = new User();
$user->name = "John Doe";
$user->email = "john.doe@example.com";
// show the email address
echo($user->email."\n");
// remove the email address (this will cause an exception)
unset($user->email);
?>
(未完待續)
想第一時間獲取最新動態,歡迎關注關注飛鴻影的博客(fhyblog)
,不定期為您呈現技術干貨。