通过引用传递__call()参数失败。有什么工作吗?

时间:2011-02-18 16:04:16

标签: php lazy-loading proxy-classes

我编写了一个相当简单的延迟加载代理类,我已经在http://blog.simonholywell.com/post/2072272471/logging-global-php-objects-lazy-loading-proxy

过去记录过了

现在当我将另一个项目转换为使用它时,我已经通过代理一个方法来绊倒,该方法的一个参数通过引用传递给它。当这通过我的代理类的__call方法时,我得到:

  

致命错误:方法LazyLoader :: __ call()不能通过/home/simon/file/name.php中的引用获取参数

任何关于如何解决或解决这个问题的聪明想法。最好不要重构需要通过引用传递的代码。

延迟加载代理类看起来像这样,但my blog post中的描述更好地解释了目的:

<?php
/**
 * @author Simon Holywell <treffynnon@php.net>
 */
class LazyLoadingProxy {
    /**
     * Where the instance of the actual class is stored.
     * @var $instance object
     */
    private $instance = null;

    /**
     * The name of the class to load
     * @var $class_name string
     */
    private $class_name = null;

    /**
     * The path to the class to load
     * @var $class_path string
     */
    private $class_path = null;

    /**
     * Set the name of the class this LazyLoader should proxy
     * at the time of instantiation
     * @param $class_name string
     */
    public function __construct($class_name, $class_path = null) {
        $this->setClassName($class_name);
        $this->setClassPath($class_path);
    }

    public function setClassName($class_name) {
        if(null !== $class_name) {
            $this->class_name = $class_name;
        }
    }

    public function getClassName() {
        return $this->class_name;
    }

    public function setClassPath($class_path) {
        if(null !== $class_path) {
            $this->class_path = $class_path;
        }
    }

    public function getClassPath() {
        return $this->class_path;
    }

    /**
     * Get the instance of the class this LazyLoader is proxying.
     * If the instance does not already exist then it is initialised.
     * @return object An instance of the class this LazyLoader is proxying
     */
    public function getInstance() {
        if(null === $this->instance) {
            $this->instance = $this->initInstance();
        }
        return $this->instance;
    }

    /**
     * Load an instance of the class that is being proxied.
     * @return object An instance of the class this LazyLoader is proxying
     */
    private function initInstance() {
        Logger::log('Loaded: ' . $class_name);
        require_once($this->class_path);
        $class_name = $this->class_name;
        return new $class_name();
    }

    /**
     * Magic Method to call functions on the class that is being proxied.
     * @return mixed Whatever the requested method would normally return
     */
    public function __call($name, &$arguments) {
        $instance = $this->getInstance();
        Logger::log('Called: ' . $this->class_name . '->' . $name . '(' . print_r($arguments, true) . ');');
        return call_user_func_array(
                array($instance, $name),
                $arguments
            );
    }

    /**
     * These are the standard PHP Magic Methods to access
     * the class properties of the class that is being proxied.
     */
    public function __get($name) {
        Logger::log('Getting property: ' . $this->class_name . '->' . $name);
        return $this->getInstance()->$name;
    }

    public function __set($name, $value) {
        Logger::log('Setting property: ' . $this->class_name . '->' . $name);
        $this->getInstance()->$name = $value;
    }

    public function __isset($name) {
        Logger::log('Checking isset for property: ' . $this->class_name . '->' . $name);
        return isset($this->getInstance()->$name);
    }

    public function __unset($name) {
        Logger::log('Unsetting property: ' . $this->class_name . '->' . $name);
        unset($this->getInstance()->$name);
    }
}

非常感谢任何帮助。

3 个答案:

答案 0 :(得分:4)

简短的回答是不通过引用传递。在99.9%的情况下,您不需要它。而在其他0.1%的情况下,你可以解决缺乏参考的问题。请记住,objects are passed by object-reference anyway因此您不需要为它们使用变量引用。

现在,就解决方法而言,我个人将其硬编码到适配器中。因此,扩展您的特定类的代理,并包含该特定方法的包装器。然后实例化新的扩展类而不是该类的核心代理。它脏了吗?绝对。但是,这是你唯一的解决方法,不重构原始类,不通过引用接受参数,或重构调用者以防止通过引用传递(无论如何都弃用)。

答案 1 :(得分:1)

这是一个技巧,通过__call魔术方法通过引用传递变量: 我现在不会有多长时间或巫婆php版本。 我在php 5.3.2上测试了

Fitst你不能在__call函数定义中通过$ reference传递$ arguments变量。因为php会出错。

首先: 这是一个代码什么不做,你想要的引用参数,但几乎是好的;)

    class A {  public $x = array();  }

    class Teszt{
        private function addElement( &$list ){
            $list->x[] = 'new element';
            return count($list->x);
        }

        public function __call($name,$arguments){
            if (!preg_match('#^add([0-9]+)Element$#', $name, $matches) || $matches[1]<1){
               trigger_error ("Function is not exists teszt::$name", E_USER_ERROR);
            }                
            $ret = null;
            for($i=0;$i<$matches[1];$i++){
                $ret = call_user_func_array(array($this,'addElement'), $arguments);
            }
            return $ret;
        }
   }

    $a = new A();
    $a->x = array('old element','old element');
    $t = new Teszt();
    $cnt = $t->add5Element($a);
    var_dump($a);
    var_dump($cnt);

输出:

    PHP Warning:  Parameter 1 to Teszt::addElement() expected to be a reference, value given in /home/gkovacs/callMagicWithReference.php on line 19
    PHP Stack trace:
    PHP   1. {main}() /home/gkovacs/callMagicWithReference.php:0
    PHP   2. Teszt->add2Element() /home/gkovacs/callMagicWithReference.php:27
    PHP   3. Teszt->__call() /home/gkovacs/callMagicWithReference.php:0
    PHP   4. call_user_func_array() /home/gkovacs/callMagicWithReference.php:19
    PHP Warning:  Parameter 1 to Teszt::addElement() expected to be a reference, value given in /home/gkovacs/callMagicWithReference.php on line 19
    PHP Stack trace:
    PHP   1. {main}() /home/gkovacs/callMagicWithReference.php:0
    PHP   2. Teszt->add2Element() /home/gkovacs/callMagicWithReference.php:27
    PHP   3. Teszt->__call() /home/gkovacs/callMagicWithReference.php:0
    PHP   4. call_user_func_array() /home/gkovacs/callMagicWithReference.php:19
    object(A)#1 (1) {
      ["x"]=>
      array(2) {
        [0]=>
        string(11) "old element"
        [1]=>
        string(11) "old element"
      }
    }
    NULL
哦,哦((( 在一个小小的'HACK'之后:

    class A {  public $x = array();  }

    class Teszt{
        private function addElement( &$list ){
            $list->x[] = 'new element';
            return count($list->x);
        }

        public function __call($name,$arguments){
            if (!preg_match('#^add([0-9]+)Element$#', $name, $matches) || $matches[1]<1){
               trigger_error ("Function is not exists teszt::$name", E_USER_ERROR);
            }                
            $ret = null;
            //Look at my hand, because i will cheat
            foreach($arguments as $k=>&$v){ } 
            //end of cheat 
            for($i=0;$i<$matches[1];$i++){
                $ret = call_user_func_array(array($this,'addElement'), $arguments);
            }

            return $ret;
        }
   }

    $a = new A();
    $a->x = array('old element','old element');
    $t = new Teszt();
    $cnt = $t->add5Element($a);
    var_dump($a);
    var_dump($cnt);

输出:

object(A)#1 (1) {
  ["x"]=>
  array(4) {
    [0]=>
    string(11) "old element"
    [1]=>
    string(11) "old element"
    [2]=>
    string(11) "new element"
    [3]=>
    string(11) "new element"
  }
}
int(4)

我们想要的。这仅适用于对象,但不适用于array-s。 有一种方法可以使用array-s来调用time-pass-by-reference ...... 像这样:(和corse你也可以用这种方式对象)

    $cnt = $t->add5Element(&$a);

在这种情况下,php生成通知......

如果可能的话,重新定义函数的最简单方法。 在我的代码中,:private functionaddElement($ list),不要在参数列表中定义为引用。当您在上一条消息中阅读时,它将起作用,因为对象通过引用自动传递 但有时,由于实现了接口或其他原因,您无法重新定义函数...

答案 2 :(得分:0)

它可以。饥肠辘辘。

    <?php
        class MyClass(){
          public function __call($name, $params)
          {
            var_dump($params);
            $params[0][0] = '222';
          }
        }
        $obj = new MyClass();    
        $info = 3;

        var_dump($info);
        $obj->setter([&$info]);
        var_dump($info);