Передача параметров __call () по ссылке не удалась. Любая работа вокруг? - PullRequest
1 голос
/ 18 февраля 2011

Я написал довольно простой ленивый загрузочный прокси-класс, который я задокументировал в прошлом как http://blog.simonholywell.com/post/2072272471/logging-global-php-objects-lazy-loading-proxy

Теперь, когда я конвертирую другой проект для работы с ним, меня отключили, проксиметод, один из параметров которого передается ему по ссылке.Когда это проходит через метод __call моего прокси-класса, я получаю:

Неустранимая ошибка: метод LazyLoader :: __ call () не может принимать аргументы по ссылке в /home/simon/file/name.php

Любые умные идеи о том, как это можно решить или обойти.Предпочтительно без рефакторинга кода, который требует передачи по ссылке, если это возможно.

Класс прокси-сервера с отложенной загрузкой выглядит следующим образом, но описание в моем блоге лучше объясняет цель:

<?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 ]

4 голосов
/ 18 февраля 2011

Краткий ответ: не переходить по ссылке. В 99,9% случаев вам это не нужно. А в остальных 0,1% вы все равно можете обойтись без ссылок. Помните, что объекты все равно передаются по ссылке на объект , поэтому вам не нужно использовать для них ссылки на переменные.

Теперь, в качестве обходного пути, я лично жестко закодировал это в адаптер. Поэтому расширите свой прокси для этого конкретного класса и включите обертку для этого конкретного метода. Затем создайте экземпляр этого нового расширенного класса вместо основного прокси для этого класса. Это грязно? Абсолютно. Но это ваш единственный обходной путь без рефакторинга исходного класса, чтобы не принимать аргумент по ссылке, или рефакторинга вызывающей стороны, чтобы предотвратить передачу по ссылке (что все равно не рекомендуется).

1 голос
/ 28 января 2013

Вот трюк для передачи переменных по ссылкам с помощью магического метода __call: Я не знаю, как долго это будет работать или в версиях ведьмы PHP. Я тестировал на php 5.3.2

Прежде всего, вы не можете передать переменную $ arguments по ссылке $ в определении функции __call. Потому что 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)

Как мы и хотели. Это работает только с объектами, но не с массивами. Есть способ с массивом-s для вызова время прохождения по ссылке ... вот так: (и конечно, вы можете использовать этот способ и с объектами)

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

В этом случае php генерирует уведомления ...

Самый простой способ переопределить функцию, если это возможно. в моем коде: private functionaddElement ($ list), не определяйте как ссылку в списке параметров. И, как вы читали в предыдущем сообщении, оно будет работать, потому что объекты передаются по ссылке автоматически Но иногда вы не можете переопределить функции из-за реализованных интерфейсов или по другим причинам ...

0 голосов
/ 29 марта 2014

Его можно. Так голоден.

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