解码OpenCart中的代理类

通常,我们认为事情是理所当然的。 如果某件事情按预期工作,那么我们就不必理会它的内部工作原理以了解其基本机制。 或者换句话说,除非遇到麻烦,否则我们不会深入研究!

同样,我一直想知道OpenCart中在底层框架中使用过的几个概念,其中一个是Proxy类。 我花了一些时间来理解它,我认为与您分享它是一件很棒的事情,因为了解新事物总是很有趣。

什么是代理类?

尽管您会在网上找到定义“代理”一词的各种材料,但Wikipedia的定义引人注目且易于理解。

在最一般的形式上,代理是一个类,充当与其他对象的接口。

因此,代理将控件委派给它打算使用的对象,并因此代表实例化的实际类。 实际上,代理设计模式是一种非常流行的模式,流行的框架会根据需要使用该模式。 考虑到代理方法本身的讨论是一个广泛的话题,不在本文的讨论范围之内,因此我将快速总结一下在大多数情况下使用的方法:

  • 充当包装器以提供其他功能。
  • 延迟昂贵对象的实例化,也称为延迟加载。

在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() 。 其中, __call()方法实现是一个重要的实现,我们将很快恢复。

代理类如何与模型一起使用

在本节中,我将说明如何像$this->model_catalog_category->getCategory($category_id)这样的调用$this->model_catalog_category->getCategory($category_id)工作。

实际上,故事始于以下陈述。

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

在引导过程中,OpenCart框架将所有通用对象存储到Registry对象,以便可以随意获取它们。 结果, $this->load调用从注册表中返回Loader对象。

Loader类提供了各种方法来加载不同的组件,但是我们在这里感兴趣的是模型方法。 让我们快速从system/engine/loader.php model方法的代码片段。

public function model($route) {
	// Sanitize the call
	$route = preg_replace('/[^a-zA-Z0-9_\/]/', '', (string)$route);
	
	// Trigger the pre events
	$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);
		
		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);
		} else {
			throw new \Exception('Error: Could not load model ' . $route . '!');
		}
	}
	
	// Trigger the post events
	$this->registry->get('event')->trigger('model/' . $route . '/after', array(&$route));
}

考虑上述示例, $route参数的值是catalog/category 。 首先,价值$route变量消毒,并按照它的触发before的事件,让其他模块听众改变的价值$route变量。

接下来,它检查注册表中请求的模型对象是否存在。 如果注册表包含请求的对象,则无需进一步处理。

如果找不到该对象,则将按照一个有趣的过程对其进行加载,这是我们在本文中所要查找的片段。

首先,它准备所请求模型的文件路径,并在存在时加载它。

...
$file  = DIR_APPLICATION . 'model/' . $route . '.php';
$class = 'Model' . preg_replace('/[^a-zA-Z0-9]/', '', $route);

if (is_file($file)) {
    include_once($file);

  ...
}
...

之后,它实例化Proxy对象。

$proxy = new Proxy();

现在,注意下一个for循环-它的作用远不止看起来。

foreach (get_class_methods($class) as $method) {
	$proxy->{$method} = $this->callback($this->registry, $route . '/' . $method);
}

在我们的例子中, $class的值应为ModelCatalogCategoryget_class_methods($class)片段加载ModelCatalogCategory类的所有方法并循环遍历。 它在循环中做什么? 让我们仔细看看。

在循环中,它调用相同类的callback方法。 有趣的是,回调方法返回以键为方法名分配给$proxy对象的可调用函数。 当然,代理对象没有任何此类属性。 它将使用__set()魔术方法即时创建!

接下来,将$proxy对象添加到注册表中,以便以后可以在需要时获取它。 仔细研究set方法的关键组成部分。 在我们的情况下,它应该是model_catalog_category

$this->registry->set('model_' . str_replace(array('/', '-', '.'), 
  array('_', '', ''), (string)$route), $proxy);

最后,它将调用after事件,以允许其他模块侦听器更改$route变量的值。

那是故事的一部分。

让我们来看看当您在控制器中使用以下命令时会发生什么情况。

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

$this->model_catalog_category代码段尝试在注册表中找到与model_catalog_category项匹配的项。 如果您想知道如何操作,只需查看system/engine/controller.php文件中的Controller类定义-它提供了实现该功能的__get()魔术方法。

正如我们刚刚讨论的那样,这应该返回分配给该特定键的$proxy对象。 接下来,它尝试在该对象上调用getCategory方法。 但是Proxy类没有实现这种方法,那怎么办呢?

__call()魔术方法可以解救! 每当您调用类中不存在的方法时,控件就会转移到__call()魔术方法中。

让我们详细研究它以了解发生了什么。 打开代理类文件,并注意该方法。

$key包含正在调用的函数的名称,即getCategory 。 另一方面, $args包含传递给方法的参数,它应该是一个数组,其中包含一个要传递的类别ID的元素。

接下来,有一个数组$arg_data ,用于存储参数的引用。 坦白说,我不确定$arg instanceof Ref代码是否有意义。 如果有人知道它为什么存在,我将很高兴学习。

此外,它尝试检查$proxy对象中$key属性的存在,并导致类似这样的情况。

if (isset($this->getCategory)) {

回想一下,我们之前使用for循环将ModelCatalogCategory类的所有方法分配为$proxy对象的属性。 为了方便起见,我将再次粘贴该代码。

...
foreach (get_class_methods($class) as $method) {
    $proxy->{$method} = $this->callback($this->registry, $route . '/' . $method);
}
...

因此它应该在那里,并且还应该向我们返回可调用的函数! 最后,它通过传递可调用函数本身和方法参数来使用call_user_func_array函数调用该可调用函数。

现在,让我们将注意力转移到函数可调用定义本身。 我将从system/engine/loader.php定义的callback方法中system/engine/loader.php

...
function($args) use($registry, &$route) {
            static $model = array();             
            
            $output = null;
            
            // Trigger the pre events
            $result = $registry->get('event')->trigger('model/' . $route . '/before', array(&$route, &$args, &$output));
            
            if ($result) {
                return $result;
            }
            
            // Store the model object
            if (!isset($model[$route])) {
                $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);
                } 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);
            } else {
                throw new \Exception('Error: Could not call model/' . $route . '!');
            }
            
            // Trigger the post events
            $result = $registry->get('event')->trigger('model/' . $route . '/after', array(&$route, &$args, &$output));
            
            if ($result) {
                return $result;
            }
                        
            return $output;
};
...

由于它是一个匿名函数,因此它以$registry$route变量的形式保存了值,这些值早先已传递给回调方法。 在这种情况下, $route变量的值应为catalog/category/getCategory

除此之外,如果我们查看该函数中的重要代码段,它将实例化ModelCatalogCategory对象并将其存储在静态$model数组中。

...
// Store the model object
if (!isset($model[$route])) {
    $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);
    } else {
        throw new \Exception('Error: Could not load model ' . substr($route, 0, strrpos($route, '/')) . '!');
    }
}
...

这是一个片段,其中包含需要使用$route变量调用的方法名称。

$method = substr($route, strrpos($route, '/') + 1);

因此,我们有一个对象引用和一个方法名称,使我们可以使用call_user_func_array函数对其进行调用。 以下代码片段就是这样做的!

...
if (is_callable($callable)) {
    $output = call_user_func_array($callable, $args);
} else {
    throw new \Exception('Error: Could not call model/' . $route . '!');
}
...

在方法的最后,通过$output变量返回结果。 是的,那是故事的另一部分!

我有意忽略了前置后置事件的代码,使您可以覆盖核心Opencart的类的方法。 使用它,您可以覆盖任何核心类方法,并根据需要进行调整。 但是,让我们将其保留一天,因为它太过复杂而无法写一篇文章!

这就是它的全部工作方式。 我希望您对那些速记OpenCart调用及其内部工作方式更有信心。

结论

我们今天刚刚讨论的是OpenCart中有趣且模棱两可的概念之一:在框架中使用Proxy方法来支持用于调用模型方法的速记约定。 我希望本文足够有趣,并丰富您的OpenCart框架知识。

我很想知道您对此的反馈,如果您觉得我应该在我的后续文章中介绍此类主题,请不要犹豫。

翻译自: https://code.tutsplus.com/tutorials/decoding-the-proxy-class-in-the-opencart--cms-28196

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值