10

いくつかの保護および/またはパブリックメソッドを持つクラスがあるとします。メソッドが呼び出されるたびにチェックを実行する必要があります。メソッドを呼び出すたびにそのチェックを行うことができます:

class Object
{
    // Methods
}

$o = new Object();

if($mayAccess) $o->someMethod();

また

if($mayAccess) $this->someMethod();

しかし、私は開発者がそれについて考えたり書いたりする必要がないことを望んでいます。私は __call を使用して行うことを考えました:

class Object
{
    public function __call($methodName, $args)
    {
        if($mayAccess) call_user_func_array($this->$methodName, $args);
    }
}

残念ながら、クラス内からメソッドを呼び出すと、目に見えないメソッドが呼び出されたときにのみ機能するため、 __call は呼び出されません。

内部呼び出しと外部呼び出しの両方でこのチェックを非表示にするクリーンな方法はありますか? 繰り返しますが、目標は、開発者がメソッドを呼び出すときに忘れないようにすることです。

前もって感謝します :)

EDIT :

これを行う別の方法があります:

class Object
{
    public function __call($methodName, $args)
    {
        if($mayAccess) call_user_func_array($methodName, $args);
    }
}

function someMethod() { }

しかし、私はもう $this を使用できなくなります。つまり、必要な保護されたメソッドはありません。

4

2 に答える 2

8

いいえ、そうは思いません。あなたができることは、プロキシを書くことです:

class MayAccessProxy {

    private $_obj;

    public function __construct($obj) {
        $this->_obj = $obj;
    }

    public function __call($methodName, $args) {
        if($mayAccess) call_user_func_array(array($this->_obj, $methodName), $args);
    }
}

これは、チェックしたいすべてのオブジェクトに対してプロキシをインスタンス化する必要があることを意味します。

$obj = new MayAccessProxy(new Object());
$obj->someMethod();

もちろん、プロキシがオブジェクト自体とまったく同じように動作することも必要です。したがって、他のマジック メソッドも定義する必要があります。

開発者にとって少し簡単にするために、次のようなことができます。

class Object {

    /**
     * Not directly instanciable.
     */
    private __construct() {}  

    /**
     * @return self
     */
    public static function createInstance() {
        $obj = new MayAccessProxy(new self());
        return $obj;
    }
}

$obj = Object::createInstance();
于 2012-11-23T18:41:18.997 に答える
0

では、すべてのメソッドを保護または非公開にするとどうなるでしょうか? (私はこれが古くて「答えられた」質問であることを知っています)

__call マジック メソッドは、存在しない非パブリック メソッドをすべてインターセプトするため、すべてのメソッドをパブリックにしないと、それらすべてをインターセプトできます。

public function __call( $func, $args )
{
  if ( !method_exists( $this, $func ) ) throw new Error("This method does not exist in this class.");

  Handle::eachMethodAction(); // action which will be fired each time a method will be called

  return $this->$func( ...$args );
}

そのおかげで、コードに何もする必要がなくなり ( __call を追加して quick を実行することを期待してreplace allください)、クラスに共通の親がある場合は、それを親に追加するだけで、もう気にする必要はありません。

しかし

このソリューションは、次の 2 つの大きな問題を引き起こします。

  • 保護された/プライベート メソッドは自動的に公開されます
  • エラーは、適切なファイルではなく __call を指しています

私たちは何ができる?

カスタムプライベート/保護

すべてのプロテクト/プライベート メソッドのリストを追加し、呼び出しの前にメソッドをパブリックに戻すことができるかどうかを確認できます。

public function __call( $func, $args )
{
  $private = [
    "PrivateMethod" => null
  ];

  if ( !method_exists( $this, $func ) ) throw new Error("This method does not exist in this class.");
  if ( isset( $private[$func] )       ) throw new Error("This method is private and cannot be called");

  Handle::eachMethodAction(); // action which will be fired each time a method will be called

  return $this->$func( ...$args );
}

多くの人にとって、これは契約を破る可能性がありますが、私は個人的にこのアプローチを、パブリック メソッドのみを持つクラス (保護するように設定) でのみ使用します。publicClassしたがって、可能であれば、メソッドをおよびに分離してprivateClass、この問題を解消することができます。

カスタム エラーとスタック

より良いエラーのために、私はこのメソッドを作成しました:

/**
  *    Get parent function/method details
  *
  *    @param int counter [OPT] The counter allows to move further back or forth in search of methods detalis
  *
  *    @return array trace It contains those elements :
  *       - function - name of the function
  *       - file     - in which file exception happend
  *       - line     - on which line
  *       - class    - in which class
  *       - type     - how it was called
  *       - args     - arguments passed to function/method
  */

protected function getParentMethod( int $counter = 0 ) {
  $excep = new \Exception();
  $trace = $excep->getTrace();
  $offset = 1;
  if ( sizeof( $trace ) < 2 ) $offset = sizeof( $trace ) - 1;
  return $trace[$offset - $counter];
}

保護されたメソッドを呼び出した前のメソッド/関数に関する詳細を返します。

public function __call( $func, $args )
{
  $private = [
    "PrivateMethod" => null
  ];

  if ( !method_exists( $this, $func ) ) {
    $details = (object) $this->getParentMethod();
    throw new Error("Method $func does not exist on line " . $details->line . ", file: " . $details->file . " invoked by " . get_class($this) . $details->type . $func . " () ");
  }

  if ( isset($private[$func]) ) {
    $details = (object) $this->getParentMethod();
    throw new Error("Method $func is private and cannot be called on line " . $details->line . ", file: " . $details->file . " invoked by " . get_class($this) . $details->type . $func . " () ");
  }

  return $this->$func( ...$args );
}
  

これは大きな問題ではありませんが、デバッグ中に混乱を招く可能性があります。

結論

このソリューションにより、クラス外からのプライベート/保護されたメソッドの呼び出しを制御できます。Anythis->Methodは省略__callし、通常は private/protected メソッドを呼び出します。

class Test {
  
  public function __call( $func, $args )
  {
    echo "__call! ";
    if ( !method_exists( $this, $func ) ) throw new Error("This method does not exist in this class.");

    return $this->$func( ...$args );
  }

  protected function Public()
  {
    return "Public";
  }

  protected function CallPublic()
  {
    return "Call->" . $this->Public();
  }

}

$_Test = new Test();
echo $_Test->CallPublic(); // result: __call! Call->Public - it uses two methods but __call is fired only once

静的メソッドに同様のものを追加する場合は、__callStaticマジック メソッドを使用します。

于 2021-03-26T14:33:51.150 に答える