开发者

PHP array references; holding references in an array for later use

开发者 https://www.devze.com 2023-03-28 06:21 出处:网络
I\'m trying to hold onto a variable reference for later use. Not certain this is even possible, but I\'m hoping I can initialize an array element, and reference it with a variable. Then, set the valu

I'm trying to hold onto a variable reference for later use.

Not certain this is even possible, but I'm hoping I can initialize an array element, and reference it with a variable. Then, set the value of said array element to something, therefore making the value accessible from the referenced variable.

For example, this works:

class Test{

    private $_vars = array();

    public function bind($key, &$var){
        $this->_vars[$key] = &$var;
        return $this;
    }

    public function fetch($key, &$var){
        $var = $this->_vars[$key];
        return $this;
    }

}

$test = new Test();
$string_set = 'This is a string';

$test->bind('string', $string_set)
    ->fetch('string', $string_get);

var_dump($string_get);
// expected:  string(16) "This is a string"
// actual:    string(16) "This is a string"

Now here's the problem; the ordering of method calls. I can't have the call() function returning a reference to $this, as the call() function needs to pass up the return value of the stored anonymous function (otherwise I'd reorder the calls to be ->call()->fetch() instead of ->fetch()->call())

Anyways, the fetch() method should be setting the appropriate element by key in $_vars to NULL (to empty any existing value, or initialize it, whichever) and then referencing that element to the passed $var.

When the anonymous function is called (after the fetch() bind开发者_开发问答ing is done), it calls bind(), now binding the element in $_vars to whatever (a $string_set containing This is a string in this case) If my logic is correct, the fetch() bound variable ($string_get in this case) should now reference the array element in $_vars which is referencing $string_set which contains This is a string.

Doesn't seem that way though. Here's the code that's failing (stripped down for brevity, but all the important parts are there)

class Test{

    private $_vars = array();
    private $_function;

    public static function factory(){
        return $test = new self(function() use(&$test){
            $string_set = 'This is a string';
            $test->bind('string', $string_set);
            return true;
        });
    }

    private function __construct($function){
        $this->_function = $function;
    }

    public function bind($key, &$var){
        $this->_vars[$key] = &$var;
        return $this;
    }

    public function fetch($key, &$var){
        $this->_vars[$key] = null;
        $var = &$this->_vars[$key]; // edited; was not assigning by reference
        return $this;
    }

    public function call(){
        return (bool) call_user_func($this->_function);
    }

}

$return = Test::factory()
    ->fetch('string', $string_get)
    ->call();

var_dump($return, $string_get);
// expected:  bool(TRUE), string(16) "This is a string"
// actual:    bool(TRUE), NULL

Am I chasing daisies here, is this even possible? Either way, I appreciate and thank you in advance for even glancing at this problem, any insight is really appreciated.

Edit: the line in fetch() - $var = $this->_vars[$key]; wasn't assigning the array element by reference. I've edited it now to $var = &$this->_vars[$key];, though it seemingly has no effect.

Bonus: If this problem is solvable, that's obviously great; I'm actually hoping that bind() can take $var by value, rather than by reference. The method signature would be changed to something like set($key, $value). Anyways, thanks again in advance.


To elaborate for the seemingly curious (looking in your direction @Tomalak) I'll provide the more complete class, and usage scenario:

class Action{

    private static $_cache = array();
    private static $_basePath;

    private $_vars = array();
    private $_function;

    public static function setBasePath($basePath){
        $basePath = rtrim($basePath, '/') . '/';
        if(!is_dir($basePath)){
            // throw exception, $basePath not found
        }
        self::$_basePath = $basePath;
    }

    public static function load($actionPath){
        $actionPath = self::$_basePath . $actionPath;
        if(array_key_exists($actionPath, self::$_cache)){
            return self::$_cache[$actionPath];
        }
        if(!is_file($actionPath)){
            // throw exception, $actionPath not found
        }
        $action = call_user_func(function() use(&$action, $actionPath){
            return require($actionPath);
        });
        if(!($action instanceof self)){
            // throw exception, $action of invalid type
        }
        self::$_cache[$actionPath] = $action;
        return $action;
    }

    public function __construct($function){
        if(!is_callable($function)){
            // throw exception, $function not callable
        }
        $this->_function = $function;
    }

    public function bindReturn($key, &$var){
        $this->_vars[$key] = &$var;
        return $this;
    }

    public function fetchInto($key, &$var){
        $this->_vars[$key] = null;
        $var = &$this->_vars[$key];
        return $this;
    }

    public function run(){
        return (bool) call_user_func_array($this->_function, func_get_args());
    }

}

############################################################################

// actions/test.php

return new Action(function($name)
    use(&$action){

        if($name == 'Alice'){
            return false;
        }

        $data = "Hi, my name is {$name}.";
        $action->bindReturn('data', $data);

        return true;

    });

############################################################################

// index.php (or whatever)

$result = Action::load('actions/test.php') // loaded
    ->fetchInto('data', $data)
    ->run('Alice');

// Failed
echo $result
    ? 'Success - ' . $data
    : 'Failed';

$result = Action::load('actions/test.php') // called from cache
    ->fetchInto('data', $data)
    ->run('Bob');

// Success - Hi, my name is Bob
echo $result
    ? 'Success - ' . $data
    : 'Failed';


What you want do is simply not possible (at least with referencces), because you cannot "redirect" a reference. Here's what happens:

$instance->fetch('foo', $myVar);

public function fetch($key, &$var){
    // Here $var is a reference to $myVar.
    $var = &$this->_vars[$key]; // now $var is a reference to $this->_vars[$key]
                                // it is not connected to $myVar anymore.
}

Here's what you can do: You can pass fetch() a reference to an array and set an element in that array to be a reference to $this->_vars[$key] or you can pass fetch() an object and set a member variable to be the reference.


Oh, sry missed the obvious: You can of course just use your bindReturn() function in the use-case you presented. That would work without problems.


Looks like you have problem with

public function fetch($key, &$var){
    $this->_vars[$key] = null;
    $var = $this->_vars[$key];
    return $this;
}

If you want to remove the key, don't set it to null, unset it:

Edit: changed the code to avoid uninitialized variable exception.

public function fetch($key, &$var){
    if(isset($this->_vars[$key]))
    {
        $var = $this->_vars[$key];
        unset($this->_vars[$key]);
    }
    else
    {
        $var = null;
    }
    return $this;
}
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号