【问题标题】:Passing __call() parameters by reference fails. Any work around?通过引用传递 __call() 参数失败。有什么解决办法吗?
【发布时间】:2011-06-29 22:30:40
【问题描述】:

我编写了一个相当简单的延迟加载代理类,我过去在 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);
    }
}

非常感谢任何帮助。

【问题讨论】:

  • 是的,我认为您可能是对的。仍然希望有人可以解决这个问题! :)

标签: php lazy-loading proxy-classes


【解决方案1】:

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

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

【讨论】:

  • 我同意您对通过参考的评估。只是希望避免进一步挖掘遗留代码。 :) 我认为重构调用而不是制作适配器会更好,并且可能涉及相同级别的工作。
【解决方案2】:

这里有一个技巧,通过 __call 魔术方法通过引用传递变量: 我现在不知道这能工作多久或在巫婆 php 版本中。 我在 php 5.3.2 上测试过

您不能在 __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 来调用 time-pass-by-reference ... 像这样:(当然你也可以用这种方式处理对象)

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

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

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

【讨论】:

    【解决方案3】:

    它可以。饿了也一样。

        <?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);
    

    【讨论】:

      猜你喜欢
      • 2012-04-21
      • 2014-02-09
      • 2013-08-17
      • 1970-01-01
      • 2010-09-24
      • 2021-07-29
      • 2015-08-27
      • 1970-01-01
      • 2010-10-07
      相关资源
      最近更新 更多