[Zephir開發實踐]用Zephir編寫PHP擴展實踐

場景描述

首先,仍是強烈推薦一下Phalcon這個框架。
php

因爲對這個框架很感興趣,因此看了其官方文檔,並在先前用PHP根據其思想寫了兩個Phalcon核心類,見連接:
html

#年前福利#Phalcon框架部分核心類的僞實現
web

http://www.oschina.net/code/snippet_256338_32995shell


今天,再次查看Phalcon官方博客說明時,其後來改用了Zephir進行重寫。延伸看了一下Zephir,發現其能夠用來開發PHP擴展,最終效果效果有點相似C語言。關於Zephir,請見連接:http://zephir-lang.com/index.htmlubuntu


這裏主要說一下使用Zephir進行擴展開發實踐過程,並延用了先前的依賴注入類DI,對其改用Zephir進行重寫。vim

得益於以前完善的測試用例,能夠繼續使用測試用例進行驗證以及TDD開發。如下爲以前測試的結果:服務器

phpunit ./test_FDI.php 
.
setUp ...

tearDown ...
.
setUp ...

tearDown ...
.
setUp ...

tearDown ...
.
setUp ...

tearDown ...
.
setUp ...
Demo::__construct()
Demo::__construct()
Demo::__construct()
Demo:: onInitialize()

tearDown ...
.
setUp ...
Demo2::__construct()
Demo2::onConstruct()
Demo2::onInitialize()
Demo2::onInitialize()

tearDown ...
.
setUp ...

tearDown ...
.
setUp ...

tearDown ...


Time: 11 ms, Memory: 3.50Mb

OK (8 tests, 30 assertions)


在進行Zephir開發前,能夠參考官方說明進行安裝。而後使用:zephir init dogstar建立開發項目。而後:框架

一、編寫Zephir代碼

~zephir$ vim ./dogstar/dogstar/Di.zep

因爲初次使用Zephir,並且其語法又介於PHP和C之間,並且發現此語言好像還有好多未完善的語法。如沒有elseif / else if這兩種用法,在對一個變量初始化爲null而後判斷isset和empty時結果都爲非預期值,不知怎麼調用匿名函數,和建立一個動態的實例(根據類名實例化,如:$a = new $className())。因此在開發過程當中,都是邊查看官方文檔,邊進行開發。函數

二、編譯構建代碼

Zephir是編譯語言,因此每次開發完後都須要從新編譯。調用命令:zephir build,若是沒有語法錯誤將會提示:測試

dogstar@ubuntu:~/projects/zephir/dogstar$ zephir build
Compiling...
Installing...
[sudo] password for dogstar: 
Extension installed!
Don't forget to restart your web server

編譯構建時,須要root權限,而且會提示須要重啓PHP。

三、重啓PHP服務器

不一樣服務器重啓PHP方式不同,如在Ubuntu環境下,則可以使用:sudo /etc/init.d/php5-fpm force-reload

結果顯示:

 * Reloading PHP5 FastCGI Process Manager php5-fpm                       [ OK ]

注意,在初次成功構建後,爲了讓PHP擴展生效,須要修改php.ini文件以添加新的擴展。這裏是:

vim /etc/php5/fpm/conf.d/dogstar.ini

而後添加如下內容:

; configuration for dogstar
extension=dogstar.so

重啓PHP服務器後,php -m | grep dogstar確認一下是否生效。

四、運行測試套件

對原來的測試套件,只須要稍微改動一下(即將實例化的類名改一下),便可對新的PHP擴展類進行測試。

最終測試結果以下:

phpunit ./test_DI.php 
.
setUp ...

tearDown ...
F
setUp ...

tearDown ...
.
setUp ...

tearDown ...
F
setUp ...

tearDown ...
E
setUp ...

tearDown ...
.
setUp ...
Demo2::__construct()
Demo2::onConstruct()
Demo2::onInitialize()
Demo2::onInitialize()

tearDown ...
.
setUp ...

tearDown ...
.
setUp ...

tearDown ...


Time: 13 ms, Memory: 3.50Mb

There was 1 error:

1) FDI_Test::testAnonymousFunction
Closure object cannot have properties

/home/dogstar/projects/php/test/test_dogstar/test_DI.php:85

--

There were 2 failures:

1) FDI_Test::testMagicFunction
Failed asserting that 'dogstar' matches expected null.

/home/dogstar/projects/php/test/test_dogstar/test_DI.php:38

2) FDI_Test::testMixed
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'name1'
+'dogstar2'

/home/dogstar/projects/php/test/test_dogstar/test_DI.php:68

FAILURES!
Tests: 8, Assertions: 19, Failures: 2, Errors: 1.

對於上面失敗的測試用例,主要問題是:

一、不知什麼緣由,調用魔法函數進行注入時,將會發生奇怪的事情。即_data的值將會發生丟失或變成key值。

二、在Zephir內未找到調用匿名函數的方法。

三、在Zephir內未找到根據一個類名建立對應實例的方法,特別如今添加了命名空間。所以採用了getInstance()的折中方案。


附最終的DI代碼:

amespace Dogstar;

class Di implements \ArrayAccess
{
    protected static _instance = null;

    protected _data = [];

    public function __construct()
    {

    }

    public static function getInstance()
    {
        if (typeof self::_instance == "NULL") {
            let self::_instance = new Di();
            self::_instance->onConstruct();
        }

        self::_instance->onInitialize();

        return self::_instance;
    }

    public function onConstruct()
    {
        //TODO
    }

    public function onInitialize()
    {
        //TODO
    }

    public function set(var key, var value)
    {
        this->_checkKey(key);

        let this->_data[key] = value;
    }

    public function get(key, defaultValue = null, boolean isShare = false)
    {
        this->_checkKey(key);

        var value;
        if !(fetch value, this->_data[key]) {
            return defaultValue;
        }

        let value = this->_data[key];

        if gettype(value) == "object" && is_callable(value) {
            //TODO how to call clourse?
            //let value = {value}();
        } else {
            if is_string(value) && class_exists(value) {
                //TODO obtain class instance by call getInstance
                //let value = new {value}();
                if is_callable([value, "getInstance"]) {
                    let value = call_user_func([value, "getInstance"]);
                }
                if gettype(value) == "object" && is_callable([value, "onConstruct"]) {
                    call_user_func([value, "onConstruct"]);
                }
                let isShare = false;
            } else {
                //TODO
                //init by array configs
            }
        }

        if !isShare && gettype(value) == "object" && is_callable([value, "onInitialize"]) {
            call_user_func([value, "onInitialize"]);
        }

        let this->_data[key] = value;

        return value;
    }

    protected function _checkKey(var key)
    {
        if empty(key) || (!is_string(key) && !is_numeric(key)) {
            throw new \Exception("Unvalid key(" . gettype(key) . "), expect to string or numeric");
        }
    }

    public function __call(name, params)
    {
        var prefix;
        let prefix = substr(name, 0, 3);

        var key;
        let key = lcfirst(substr(name, 3, strlen(name)));

        var value = null;
        fetch value, params[0];

        if prefix == "get" {
            return this->get(key, value);
        }

        if prefix == "set" {
            this->set(key, value);
            return;
        }

        throw new \Exception("Call to undefined method Di::" . name . "()");
    }

    public function __set(key, value)
    {
        this->set(key, value);
    }

    public function __get(key)
    {
        return this->get(key, null, true);
    }

    public function offsetSet(offset, value)
    {
        this->set(offset, value);
    }

    public function offsetGet(offset)
    {
        return this->get(offset);
    }

    public function offsetUnset($offset)
    {
        unset(this->_data[offset]);
    }

    public function offsetExists(offset)
    {
        var value;

        return fetch value, this->_data[offset];
    }
}
相關文章
相關標籤/搜索