【问题标题】:Automatically decorate every method in a class自动装饰类中的每个方法
【发布时间】:2015-03-07 13:50:20
【问题描述】:

我想在每次在类中使用方法(不管是私有的、受保护的还是公共的)之前和之后添加一些逻辑。

例如:

class Service 
{
    function test1() {
        Log:start(__METHOD__);
        someLogicInThere(); ....
        Log:end(__METHOD__);
    }

    function test2() {
        Log:start(__METHOD__);
        someLogicInThere(); ....
        Log:end(__METHOD__);
    }
    ...
}

我的想法是最终有这样的东西:

/**
 * @LogDecorate
 */
class Service
{
    function test1() {
        someLogicInThere();
    }

    function test2() {
        someLogicInThere();
    }
    ...
}

使用注释并不重要。有什么办法吗?

【问题讨论】:

    标签: php design-patterns decorator


    【解决方案1】:

    正如您的问题标题已经暗示的那样,您可以为此使用Decorator Pattern。我不太确定这里是否需要全栈装饰器模式。如果这是一个非常简单的用例,那么这样的事情就足够了。

    您可以做的是扩展类并将所有调用“路由”到扩展类。然后前后加一些逻辑,中间调用父方法。像这样的:

    class Service {
        function method1() {
            doSomeFunkyStuff();
        }
    
        function method2() {
            doSomeOtherFunkyStuff();
        }
    }
    
    class DecoratedService extends Service {
        function method1() {
            Log::start(__METHOD__);
            parent::method1();
            Log::end(__METHOD__);
        }
    
        function method2() {
            Log::start(__METHOD__);
            parent::method2();
            Log::end(__METHOD__);
        }
    }
    
    $service = new DecoratedService();
    $service->method1();
    $service->method2();
    

    现在您可以选择使用原始的Service 或使用DecoratedService。功能是相同的,如果 Service 发生更改,则 DecoratedService 不必更改,假设方法名称不会更改(这实际上是一件坏事)。

    但也请查看 wiki 页面(或任何其他资源)以充分了解装饰器模式的意图。这(上图)可能不是您问题的理想解决方案。

    EDIT 先生,按要求更自动化一点。

    由于您无法更改方法的可见性,因此使用魔法 __call() 不起作用(因为子也可以访问公共或受保护的父方法)。但是,您可以做的是创建自己的调用方法!

    class DecoratedService extends Service {
        function call($method) {
            if(!method_exists(parent, $method)) {
                return false; // OR:
                throw Exception;
                // OR handle this case some other way
            }
    
            Log::start(array(parent, $method));
            call_user_func(array(parent, $method));
            Log::end(array(parent, $method));
        }        
    }
    
    $service = new DecoratedService;
    $service->call('method1');
    

    【讨论】:

    • 我想使用更自动的东西。我的课程已经创建,为每个课程创建一个装饰器将是一场噩梦
    • 好吧,您应该为每个要添加此功能的类创建一个类似装饰器的类,或者直接使用它。创建这样的东西来记录对每个类的每个对象的每个调用都是 hacky 和非常不可取的。
    • 什么是“hacky”解决方案?
    • 嗯,你可以。覆盖spl_autoload_register() 方法以向(装饰)类添加功能。但我不能警告你足够多:这非常hacky,你甚至不应该尝试这个......
    【解决方案2】:

    我猜,这是智能参考模式的典型案例(代理模式和装饰器模式的混合)。

    class A {
        function test1() {
            echo 'TEST 1', PHP_EOL;
        }
    
        function test2() {
            echo 'TEST 1', PHP_EOL;
        }
    }
    
    class ProxyA {
        protected $wrapped;
    
        public function __construct($wrapped) {
            $this->wrapped = $wrapped;
        }
    
        public function __call($name, $args) {
            echo 'Log:start';
            $this->wrapped->$name($args);
            echo 'Log:end';
        }
    }
    
    $proxy = new ProxyA(new A());
    $proxy->test1();
    

    但它只适用于公共方法。

    将 Smart Reference 与来自 @giorgio 和 @yceruto 的 DecoratedService::call() 方法混合使用可以覆盖所有方法,或者只实现 __call() 两次:

    class A {
        public function test1() {
            echo 'TEST 1', PHP_EOL;
        }
    
        private function test2() {
            echo 'TEST 2', PHP_EOL;
        }
    
        public function __call($name, $args) {
            if (method_exists($this, $name)) {
                $this->$name($args);
            }
        }
    }
    
    class ProxyA {
        protected $wrapped;
    
        public function __construct($wrapped) {
            $this->wrapped = $wrapped;
        }
    
        public function __call($name, $args) {
            if (method_exists($this->wrapped, $name)) {
                echo 'Log:start';
                $this->wrapped->$name($args);
                echo 'Log:end';
            }
        }
    }
    
    $proxy = new ProxyA(new A());
    $proxy->test0(); // Nothing to do
    $proxy->test1(); // Done
    $proxy->test2(); // Done
    

    【讨论】:

      【解决方案3】:

      使用神奇的 __call 方法可能会让您轻松:

      class Service 
      {
          public function test1() {
              echo 'TEST 1', PHP_EOL;
          }
      
          protected function test2() {
              echo 'TEST 2', PHP_EOL;
          }
      
          public function __call($method, $args) {
              echo 'Some stuff before', PHP_EOL;
              $returnValue = $this->$method($args);
              echo 'Some stuff after', PHP_EOL;
              return $returnValue;
          }
      }
      
      $x = new Service();
      $x->test2();
      $x->test1();
      

      请注意,如果可以从类外部访问该方法(如test1),则不会调用__call();它仅在所涉及的方法受保护或私有时执行;如果在对象内部调用它们也不会触发

      【讨论】:

      • 我无法更改每个方法的类型。
      猜你喜欢
      • 2012-04-21
      • 1970-01-01
      • 1970-01-01
      • 2014-01-14
      • 2011-03-04
      • 2020-01-11
      • 1970-01-01
      • 1970-01-01
      • 2018-09-12
      相关资源
      最近更新 更多