zoukankan      html  css  js  c++  java
  • 深入解析OpenCart的代理类proxy

    1.什么是代理类

      代理类指的是连接远程对象或不可见对象的接口,通常被客户端调用来连接真实的服务对象。更准确的定义参见维基百科 

    2.代理的作用

    • 作为一个包装类,提供额外的功能
    • 延迟加载 

      在本文讲到的opencart框架中,从实例化的角度讲,我们认为这是延迟加载(按需加载)。当然, 我们可以认为这是为基础代理类库提供额外的功能。以下是opencart的基础代理类(文件路径:system/engine/proxy.php)

    <?php
    class Proxy {
        public function __get($key) {
            return $this->{$key};
        }    
        
        public function __set($key, $value) {
             $this->{$key} = $value;
        }
        
        public function __call($key, $args) {
            $arg_data = array();
            
            $args = func_get_args();
            
            foreach ($args as $arg) {
                if ($arg instanceof Ref) {
                    $arg_data[] =& $arg->getRef();
                } else {
                    $arg_data[] =& $arg;
                }
            }
            
            if (isset($this->{$key})) {        
                return call_user_func_array($this->{$key}, $arg_data);    
            } else {
                $trace = debug_backtrace();
                
                exit('<b>Notice</b>:  Undefined property: Proxy::' . $key . ' in <b>' . $trace[1]['file'] . '</b> on line <b>' . $trace[1]['line'] . '</b>');
            }
        }
    }

    在该代理类中,我们可以看到,它只声明了三个魔术方法,__get()(当获取类中不存在或者受保护的属性时,自动调用该方法),__set()(当设置类中不存在或者受保护的属性时,自动调用该方法),__call()(当调用类中不存在的方法是,自动调用该方法)。

    3.代理类是怎么在opencart中的model类起到作用的呢?

      在本节中,我就以opencart中常见的调用方法$this->model_catalog_category->getCategory($category_id)为例来分析。 

      事实上,我们在调用该方法之前,我们需要执行如下一条代码:

    $this->load->model('catalog/category');

      我们分析下这条代码怎么通过opencart框架执行的:

      1.$this->load在model类中没有定义,自动调用model类中的魔术方法__get(),$this->load等同于$this->registry->get(‘load’),

      2.opencart框架将所有实例化的对象注册到Register这个对象中,调用某个实例化的对象方法:$this->register->get($key),

      3.通过以上分析,我们知道此时已经进入opencart中的loader类(文件路径:system/engine/loader.php)中的model方法

    public function model($route) {
        // 剔除一些非法的字符
        $route = preg_replace('/[^a-zA-Z0-9_/]/', '', (string)$route);
        
        // 触发前缀事件,如果在配置文件中配置了'model'.$route.'/before'路径,通过该方法,可以改变$route的值,
        $this->registry->get('event')->trigger('model/' . $route . '/before', array(&$route));
        
        //如果还没有注册,则进入
        if (!$this->registry->has('model_' . str_replace(array('/', '-', '.'), array('_', '', ''), $route))) { 
            $file  = DIR_APPLICATION . 'model/' . $route . '.php';
            $class = 'Model' . preg_replace('/[^a-zA-Z0-9]/', '', $route); //注意PHP中,类名,方法名,函数名是不区分大小写的。
            
            if (is_file($file)) {
                include_once($file);
    
                $proxy = new Proxy(); //实例化基础代理类
                
                foreach (get_class_methods($class) as $method) { //循环类中的方法
                    $proxy->{$method} = $this->callback($this->registry, $route . '/' . $method); //返回匿名函数作为代理类属性值
                }
                
                $this->registry->set('model_' . str_replace(array('/', '-', '.'), array('_', '', ''), (string)$route), $proxy); //注册代理对象,以便可以通过$this->model_catalog_category的形式访问代理对象
            } else {
                throw new Exception('Error: Could not load model ' . $route . '!');
            }
        }
        
        // 触发后缀事件(需要配置)
        $this->registry->get('event')->trigger('model/' . $route . '/after', array(&$route));
    }

       现在我们再来分析下以下这条代码:

    $this->model_catalog_category->getCategory($category_id)

      1.$this->model_catalog_category在Model类中没有找到,会自动调用Model类中的魔术方法__get(),$this->model_catalog_category等同于$this->register->get('model_catalog_category');

      2.通过上面的分析,我们已经知道将实例化的代理类注册到了Register这个对象上,因此以上代码等同于$proxy->getCategory($category_id);

      3.在proxy代理类中,由于没有getCategory()方法,自动调用了魔术方法__call();

      接下来我们自己分析一下proxy代理类中__call()方法

        public function __call($key, $args) {  //$key:方法名,$args:以数组的形式代表参数的集合
            $arg_data = array();
            
    
            $args = func_get_args(); //返回包含方法名以及参数的数组,即array($key,$args);
            
            foreach ($args as $arg) {
                if ($arg instanceof Ref) {
    
                    $arg_data[] =& $arg->getRef();
                } else {
                    $arg_data[] =& $arg;
                }
            }
            
            if (isset($this->{$key})) {        //$this->{$key} 指的是上文提到的通过$this->callback()返回的匿名函数
                return call_user_func_array($this->{$key}, $arg_data);    //执行该匿名函数,$arg_data:参数数组
            
            } else {
                $trace = debug_backtrace();
                
                exit('<b>Notice</b>:  Undefined property: Proxy::' . $key . ' in <b>' . $trace[1]['file'] . '</b> on line <b>' . $trace[1]['line'] . '</b>');
            }
        }

      通过call_user_func_array(),最终执行到system/engine/loader.php中callback()方法里面的代码

    $output = null;
                
                // 触发前缀事件,该事件可以不改动核心代码的前提下,按照自己的需求改动做改动。(需要配置)
                $result = $registry->get('event')->trigger('model/' . $route . '/before', array(&$route, &$args, &$output));
                
                if ($result) {
                    return $result;
                }
                
                // 存贮model对象
                if (!isset($model[$route])) {
                    //本例中,$route是指catalog/category/getCategory
                    $file = DIR_APPLICATION . 'model/' .  substr($route, 0, strrpos($route, '/')) . '.php';
                    $class = 'Model' . preg_replace('/[^a-zA-Z0-9]/', '', substr($route, 0, strrpos($route, '/')));
    
                    if (is_file($file)) {
                        include_once($file);
                    
                        $model[$route] = new $class($registry); //实例化Modelcatagorycategory
                    } else {
                        throw new Exception('Error: Could not load model ' . substr($route, 0, strrpos($route, '/')) . '!');
                    }
                }
    
                $method = substr($route, strrpos($route, '/') + 1);//方法名
                
                $callable = array($model[$route], $method);
    
                if (is_callable($callable)) {
                    $output = call_user_func_array($callable, $args);//最终执行了Modelcategorycategory类中的getCategory()方法。
                } else {
                    throw new Exception('Error: Could not call model/' . $route . '!');
                }
                
                // 触发后缀事件(需要配置)
                $result = $registry->get('event')->trigger('model/' . $route . '/after', array(&$route, &$args, &$output));
                
                if ($result) {
                    return $result;
                }
                            
                return $output;

    以上就是整个opencart框架中Model类配合proxy类的执行流程

  • 相关阅读:
    ClipboardJS实现将页面内容复制到剪贴板
    全文索引FULLTEXT 的使用
    [1].Array.diff
    Leetcode——Queue队列
    Matplotlib——scatter散点图
    Matplotlib的一些小细节——tick能见度
    Matplotlib的一些小细节——Annotation标注
    Matplotlib的一些小细节——Legend图例
    Matplotlib基本知识(figure & axes
    锁升级简记
  • 原文地址:https://www.cnblogs.com/freelyflying/p/7091051.html
Copyright © 2011-2022 走看看