Why my Zephir DI can not use magic function correctly? Thanks!

Hi guys,

Phalcon is the best Framework I have ever seen, and Zephir surprise me, too. I have read the Zephir documents and explored a DI as Phalcon DI, and test it with phpunit. Amazing! it is easier more to explore php extensions with Zephir, but I still have some questions as bellow.

Q1: unexpected result return when using magic functions to register services. As the test show:

$di = new Dogstar\Di::getInstance();
$di->setName('dogstar');
$this->assertEquals($this->fdi->getName(), 'dogstar');

It FAILED! it returned null not 'dogstar'. I debug it with my code, i found that DI::_$data become empty after I use Setter function. but i don't know why.

Q2: how can I call closure functions?

My DI Zephir Code as below:

//DI.zep
namespace 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) {
                var tmp;
                let tmp = create_instance(value);
                let value = tmp;
                if gettype(value) == "object" && is_callable([value, "onConstruct"]) {
                    call_user_func([value, "onConstruct"]);
                }
                let isShare = false;
                return value;
            } 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];
    }
}

Could somebody help?



82.2k

This line, it's not supossed to work that way:

if (typeof self::_instance == "NULL") {

If you want to check if a variable is null:

if typeof self::_instance === null {

This:

if gettype(value) == "object" {

Can be written as:

if typeof value == "object" {

If you find a bug out you can submit a new issue in https://github.com/phalcon/zephir

edited Jul '14

Thanks @Phalcon, I used to check the type with typeof rather than gettype, however, i found it DID NOT work! Now I changed the code as your advice:

        //if (typeof self::_instance == "NULL") {
        if typeof self::_instance === null {
            let self::_instance = new Di();
            self::_instance->onConstruct();
        }

when I tried to build, it shows: Warning: Possible invalid comparison for 'typeof' operator with non-string in /home/dogstar/projects/zephir/dogstar/dogstar/Di.zep on 17 [invalid-typeof-comparison]

      if typeof self::_instance === null {
    -------------------------------------^

Compiling... Installing...

And then when I run the phpunit tests to call Dogstar\Di::getInstance();, Segmentation fault (core dumped) again.

BACK TO THE QUESTION.

To trace the process, I added a method getData() to DI class as below:

    public function getData()
    {
        echo "#getData\n";
        var kk, vv;
        for kk, vv in this->_data {
            echo kk . "--" . gettype(vv) . "\n";
        }
        return this->_data;
    }

and then run the test below:

    public function testMixed()
    {
        $this->fdi->name1 = 'dogstar1';
        $this->assertEquals($this->fdi->name1, 'dogstar1');
        $this->assertEquals($this->fdi->getName1('2013'), 'dogstar1');
        $this->assertEquals($this->fdi->name1, 'dogstar1');
        var_dump($this->fdi->getData());

        $this->fdi->setName2('dogstar2');
        var_dump($this->fdi->getData());
        $this->assertEquals($this->fdi->name2, 'dogstar2');  //FAILED HERE!!! why?!
        $this->assertEquals($this->fdi->getName2('2013'), 'dogstar2');
        $this->assertEquals($this->fdi->name2, 'dogstar2');

        $this->fdi->set('name1', 'dogstar3');
        $this->assertEquals($this->fdi->name1, 'dogstar3');
        $this->assertEquals($this->fdi->getName1('2013'), 'dogstar3');
        $this->assertEquals($this->fdi->name1, 'dogstar3');

    }

It FAILED! and I can see the trace info as below:

#getData
name1--string
array(1) {
  ["name1"]=>
  string(8) "dogstar1"
}
#getData
name1--string
name2--NULL
array(2) {
  ["name1"]=>
  string(8) "dogstar1"
  ["name2"]=>
  NULL
}

WHY It ONLY works with property setter or array index, but not method get or getXXX ?!

I debug it before and know that, but i could not tell why.



82.2k

It must be:

if self::_instance === null {

Thanks @Phalcon again! And I add an issue to make the question more clear, here is the link: https://github.com/phalcon/zephir/issues/438