2

私は過去にhttp://blog.simonholywell.com/post/2072272471/logging-global-php-objects-lazy-loading-proxyで文書化したかなり単純な遅延読み込みプロキシ クラスを作成しました。

現在、別のプロジェクトを変換してそれを使用するときに、パラメーターの 1 つが参照によって渡されたメソッドをプロキシすることでつまずきました。これがプロキシ クラスの __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);
    }
}

どんな助けでも大歓迎です。

4

3 に答える 3

4

簡単な答えは、参照渡しをしないことです。99.9% の場合、必要ありません。そして、それらの他の 0.1% では、とにかく参照の欠如を回避できます。いずれにせよ、オブジェクトはオブジェクト参照によって渡されるため、変数参照を使用する必要がないことに注意してください。

さて、回避策として、私は個人的にそれをアダプターにハードコーディングします。したがって、その特定のクラスのプロキシを拡張し、その特定のメソッドのラッパーを含めます。次に、そのクラスのコア プロキシの代わりに、その新しい拡張クラスをインスタンス化します。汚れていますか?絶対。ただし、元のクラスをリファクタリングして引数を参照渡ししないようにするか、呼び出し元をリファクタリングして参照渡しを防止しない限り、これが唯一の回避策です (これは非推奨です)。

于 2011-02-18T16:11:29.933 に答える
1

__call マジックメソッドを介して参照によって変数を渡すためのトリックを次に示します。これがどのくらいの時間機能するか、または witch 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

ohhh :((( 少し「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 を使用して call-time-pass-by-reference する方法があります ... 次のように: (もちろん、オブジェクトでもこの方法を使用できます)

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

この場合、php は通知を生成します ...

可能であれば関数を再定義する最も簡単な方法。私のコードでは、: private functionaddElement($list) 、パラメーター リストで参照として定義しないでください。そして、前のメッセージで読んだように、オブジェクトは参照によって自動的に渡されるため、機能します。ただし、実装されているインターフェイスやその他の理由により、関数を再定義できない場合があります...

于 2013-01-28T17:46:00.327 に答える