[Zephir开发实践]用Zephir编写PHP扩展实践

场景描述

首先,还是强烈推荐一下Phalcon这个框架。

由于对这个框架很感兴趣,所以看了其官方文档,并在先前用PHP根据其思想写了两个Phalcon核心类,见链接:

#年前福利#Phalcon框架部分核心类的伪实现

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


今天,再次查看Phalcon官方博客说明时,其后来改用了Zephir进行重写。延伸看了一下Zephir,发现其可以用来开发PHP扩展,最终效果效果有点类似C语言。关于Zephir,请见链接:http://zephir-lang.com/index.html


这里主要说一下使用Zephir进行扩展开发实践过程,并延用了先前的依赖注入类DI,对其改用Zephir进行重写。

得益于之前完善的测试用例,可以继续使用测试用例进行验证以及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创建开发项目。然后:

1、编写Zephir代码

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

由于初次使用Zephir,而且其语法又介于PHP和C之间,而且发现此语言好像还有好多未完善的语法。如没有elseif / else if这两种用法,在对一个变量初始化为null然后判断isset和empty时结果都为非预期值,不知怎么调用匿名函数,和创建一个动态的实例(根据类名实例化,如:$a = new $className())。所以在开发过程中,都是边查看官方文档,边进行开发。

2、编译构建代码

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。

3、重启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确认一下是否生效。

4、运行测试套件

对原来的测试套件,只需要稍微改动一下(即将实例化的类名改一下),即可对新的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.

对于上面失败的测试用例,主要问题是:

1、不知什么原因,调用魔法函数进行注入时,将会发生奇怪的事情。即_data的值将会发生丢失或变成key值。

2、在Zephir内未找到调用匿名函数的方法。

3、在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];
    }
}


你可能感兴趣的:([Zephir开发实践]用Zephir编写PHP扩展实践)