3

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() binding 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';
18
  • I don't understand why fetch needs to set anything to null. Maybe I'm just being daft. Commented Aug 12, 2011 at 17:46
  • @Tomalak Geret'kal - Its because otherwise the index is not initialized to anything, and notices are thrown, because call() (and subsequent functions) have not yet bound values to the array. Commented Aug 12, 2011 at 17:49
  • The fact that bind happens under call, which calls a function that was passed into the constructor but not immediately invoked, is completely confusing me. This is very complex to parse; there must be a better way to achieve your wider goal. Commented Aug 12, 2011 at 17:51
  • @Tomalak Geret'kal - Unfortunately no, there isn't a better way. I've excluded a great deal of logic associated with setting the anonymous function, etc., to isolate the actual issue and make it more comprehensible. I'm actually moving away from a complicated approach, in favor of this. Commented Aug 12, 2011 at 18:15
  • 1
    Thanks @Tomalak Geret'kal - As the ocean said to the dirt, I appreciate the sediment :P Really though, I'd be happy to get some feedback once it's out of alpha. I'll be linking up in my profile someday soon, the next time we cross on a question, I'll make mention. Commented Aug 12, 2011 at 23:10

2 Answers 2

2

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.

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks @Chronial - Thanks for your suggestion; so there isn't a way to point $myVar at $this->_vars[$key] without resorting to one of the workarounds you suggested? Also, what did you mean exactly regarding your edit about bindReturn()?
My goodness, I assume it was you meant, but bindReturn() is doing the job that fetchInto() should have been. By swapping the operands in the fetchInto() reference assignment, it works. I was overwriting my reference assignment, as your pre-edit answer had explained. My, what a silly error. Thanks again Chronial!
Yes, that's exactly what I meant ;). Just out of curiosity: What is the bindReturn() function supposed to do?
bindReturn() was intended to bind values from in the scope of the callback to an outside variable. Now that I've revised my design a bit, I realize that there are no circumstances under which "binding" a value to be returned provides any advantage, and simply returning by value is best. Furthermore, when binding values by reference, an intermediary variable is always necessary, so returning values directly from function calls is not possible. I'll update my answer with the more finalized version sometime a bit later. Check back if you want. Thanks again :)
2

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;
}

3 Comments

(deleted my comment because I though I misread your answer, but it was the same in the end anyways) Thanks @Dani - Unfortunately that doesn't work. Setting NULL instead of unset() does double duty, in that it empties the existing value, or initializes the array element. If I use unset(), notices get throw regarding the undefined index, since the values haven't been set by call() yet.
@Bracketworks: setting to null does not empty existing variable. it set the variable its referencing to null, the reference still holds. you can try to use isset (I'll edit the answer soon)
Ah, good point. However since the fetch() call is occurring before any variables are set, setting to NULL at that point shouldn't affect even the referenced variable's values (nothing has been referenced via bind() yet)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.