zephir中引用赋值和参数的传引用实现

近期在修改一些Phalcon的底层实现时,发现zephir并未提供引用赋值和参数的传引用特性,导致某些特定场景的代码无法实施,例如循环调用中要求不能抛出异常,只能用一个最外层的handle来存储循环中异常或错误,这就要求必须使用参数的传引用特性,所以只能通过一些变通方法来变通实现。

引用赋值

使用&进行引用赋值,编译时会引发异常并提醒你官方并不支持引用赋值,但是可以通过Optimizer扩展来实现引用赋值。

ext/zep_ref.h :

#ifndef ZEP_REF_H
#define ZEP_REF_H

#include <Zend/zend.h>
#include <Zend/zend_API.h>

void ZEP_REF(zval* dst, zval* src);

#endif

ext/zep_ref.c :

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

#include <php.h>
#include "php_ext.h"
#include "ext.h"

#include "kernel/main.h"
#include "kernel/exit.h"
#include <Zend/zend.h>
#include <Zend/zend_API.h>
#include "zep_ref.h"

void ZEP_REF(zval* dst, zval* src)
{
   int refcount = Z_REFCOUNT_P(dst);
   zval_dtor(&dst);
   ZVAL_NEW_REF(dst, src);
   Z_SET_REFCOUNT_P(dst, refcount);
}

ext/opmitizers/ZepRefOptimizer.php

<?php
namespace Zephir\Optimizers\FunctionCall;

use Zephir\Call;
use Zephir\CompilationContext;
use Zephir\CompiledExpression;
use Zephir\CompilerException;
use Zephir\Optimizers\OptimizerAbstract;

class ZepRefOptimizer extends OptimizerAbstract
{
    public function optimize(array $expression, Call $call, CompilationContext $context)
    {
        $context->headersManager->add('zep_ref');
        if (count($expression['parameters']) != 2) {
            throw new CompilerException("'zep_ref' requires two parameter", $expression);
        }

        $resolvedParams = $call->getReadOnlyResolvedParams($expression['parameters'], $context, $expression);

        $context->codePrinter->output(
            sprintf('ZEP_REF(%s, %s);', $resolvedParams[0], $resolvedParams[1])
        );

        return new CompiledExpression('null', null, $expression);
    }
}

在config.json里添加:

"optimizer-dirs": [
    "ext/optimizers"
],
"extra-sources": [
    "zep_ref.c"
],

使用很简单

var a = "采蘑菇的小姑娘";
var b;
zep_ref(a, b);
let a = "爱吃鱼的大脸猫";
echo b;

输出的是: 爱吃鱼的大脸猫

参数的传引用

方法传参使用&并不会引发异常,但是也不会达到预期的结果,在php底层,标记参数是否是按值传递,还是按应用传递仅仅是一个标记值的0与1的区别,但事实上在zephir中这个问题要复杂的多,比如考虑匿名函数,考虑use(),所以无法从扩展层面解决,除非你有能力修改编译器代码。所以只能通过一些其它的方法来模拟实现参数的传引用。

将需要传引用的参数作为返回值返回通常是一种可以解决大多数需要的办法,但是并不是所有场景都可以这么做,例如匿名函数作为方法参数且多层循环调用或者某一层无法返回值得时候,就必须考虑使用参数传引用。

一个方法是利用超全局变量进行读写,但是要注意健名可能存在的重复:

var key = uuid();
var str = "采蘑菇的小姑娘";
let _ENV[key] = str;

var callback = function () use (key) {
    let _ENV[key] = "爱吃鱼的大脸猫";
}

call_user_func(callback, []);
let str = _ENV[key];
echo str;

另一种方法是利用对象进行传递,zephir和php一样也是将对象指针传递到方法内,所以只需要修改对象的属性值,就可以在方法之外将其取出来 Ref.zep :

namespace Anas\Support;

class Ref
{
    private values = [];

    public function __contract(array values = [])
    {
        if ! empty(values) {
            let this->values = values;
        }
    }

    public function set(string name, value)
    {
        let this->values[name] = value;
    }

    public function get(string name)
    {
        var value;
        let value = isset(this->values[name]) ? this->values[name] : null;

        return value;
    }
}

使用:

var key = "hello";
var str = "采蘑菇的小姑娘";
var ref;
let ref = new Ref();
ref->set(key, str);

var callback = function () use (key) {
    ref->set(key, "爱吃鱼的大脸猫");
}

call_user_func(callback, []);
let str = ref->get(key);
echo str;

这些方法都是变通实现,和真正的传引用在性能上还有有差异,不是万不得已还是别用。

End!

发表回复

您的电子邮箱地址不会被公开。