【问题标题】:PHP - __call all the timePHP - 一直__call
【发布时间】:2012-11-12 02:59:40
【问题描述】:

假设我们有一个包含多个受保护和/或公共方法的类。 每次调用方法时我都需要进行检查。每次调用方法时我都可以进行检查:

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,这意味着没有受保护的方法,而我确实需要。

【问题讨论】:

  • 请查看call_user_func_array 手册页。我在这篇文章中纠正了 3 次相同的错误:如果您希望方法可调用,则必须使用 array($object, $method) 来描述它。
  • @Ninsuo 不要那样做。您绝对不应该更正问题中的代码。提问者发布的代码需要保持原样。

标签: php methods call magic-methods


【解决方案1】:

不,我不这么认为。你可以做的是写一个proxy:

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

【讨论】:

  • 感谢您的帮助。这仍然需要开发人员考虑代理并编写它,所以我应该考虑另一种方法来做到这一点。
  • 问题是,你没有来实例化这些代理对象——你仍然可以直接使用new Object()。所以这仍然让开发人员记住始终创建/使用代理。不完全繁琐,但仍然有点容易出错......
  • 如果要包含命名构造函数,您可能希望确保真正的构造函数不是公开的。这对于防止裸露的物体四处飘荡大有帮助。
  • 这不是单例模式。只是让你知道。 :) 它只是一个命名构造函数。
  • 真的吗?在这种情况下是什么?
【解决方案2】:

那么,如果您将所有方法设为受保护或私有,该怎么办? (我知道这是一个古老且“已回答”的问题)

__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 并快速完成replace all),如果您的类有共同的父类,那么您可以将其添加到父类,而不再关心。

但是

这个解决方案产生了两个主要问题:

  • 受保护/私有方法将自动对公众可用
  • 错误将指向 __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 );
}

对于许多人来说,这可能会破坏交易,但我个人只在只有公共方法(我将其设置为受保护)的类中使用这种方法。因此,如果可以,您可以将方法分成publicClassprivateClass 并消除此问题。

自定义错误和堆栈

为了更好的错误,我创建了这个方法:

/**
  *    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 );
}
  

这不是什么大问题,但可能会在调试时导致一些混乱。

结论

此解决方案允许您从 CLASS 外部控制对私有/受保护方法的任何调用。任何this-&gt;Method 都会省略__call 并且通常会调用私有/受保护方法。

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 魔术方法。

【讨论】:

    猜你喜欢
    • 2010-12-03
    • 2011-08-02
    • 1970-01-01
    • 2011-03-20
    • 2011-07-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多