• 日常搜索
  • 百度一下
  • Google
  • 在线工具
  • 搜转载

在OpenCart中解理Proxy类

很多时候,我们认为事情是理所当然的。如果某些东西按预期工作,我们就不必担心它的内部运作来理解底层机制。或者换一种说法,在遇到某种麻烦之前,我们不会深入研究!

同样,我一直想知道 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()方法实现是一个重要的部分,我们很快就会回到它。

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

在本节中,我将解释调用 like 是如何$this->model_catalog_category->getCategory($category_id)开箱即用的。

事实上,故事从以下陈述开始。

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

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

该类Loader提供了各种方法来加载不同的组件,但我们在这里感兴趣的是 模型方法让我们快速 modelsystem/engine/loader.php.

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变量的值。

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

如果找不到对象,它会按照一个有趣的过程来加载它,这就是我们在本文上下文中寻找的片段。

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

...
$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在注册表中查找键的匹配项。如果您想知道如何做,只需查看文件Controller中的类定义 system/engine/controller.php——它提供了执行此 操作的__get()魔术方法。

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

神奇的__call()方法来拯救!每当您调用类中不存在的方法时,控制权就会转移到 __call()魔术方法。

让我们详细探索它以了解发生了什么。打开 Proxy 类文件并注意该方法。

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

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

此外,它会尝试检查对象中是否存在 $key属性, $proxy结果是这样的。

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

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

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

所以它应该在那里,它也应该返回我们可调用的函数!最后,它 call_user_func_array通过传递可调用函数本身和方法参数来调用可调用函数。

现在,让我们将注意力转移到函数可调用定义本身。我将从中 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 框架知识。


文章目录
  • 什么是代理类?
  • 代理类如何与模型一起使用
  • 结论