本篇主要分析控制器工厂,这个很重要,因为在页面上操作时,其实都是操作相关模块的相关方法,而相关模块对应到SuarCRM中都是通过控制器工厂来加载各个不同的控制器的。
class SugarApplication
{
```
function execute()
{
```
$this->controller = ControllerFactory::getController($module);
if ($this->controller->action === 'sidecar' ||
(
$this->controller->action === 'index' && $this->controller->module === 'Home' &&
(empty($_REQUEST['entryPoint']) || (isset($_REQUEST['action']) && $_REQUEST['action'] === 'DynamicAction'))
) ||
empty($_REQUEST)
) {
// check for not authorised users
$this->checkMobileRedirect();
$this->controller->action = 'sidecar';
$this->controller->execute();
return;
}
```
}
```
}
上面的逻辑是假设在地址栏上输入http://sugar来的,本篇都是以此为基础的。
/**
* ./include/MVC/Controller/ControllerFactory.php
* 控制器工厂主要用来依据传过来的各个模块生成相应的控制器实例的
* 首先匹配的是模块的控制器类,没有的话再匹配Sugar自己的控制器类
* "modules/{$module}/controller.php" > 'include/MVC/Controller/SugarController.php'
*/
class ControllerFactory
{
/**
* Obtain tain an instance of the correct controller.
*
* @return an instance of SugarController
*/
function getController($module)
{
if(SugarAutoLoader::requireWithCustom("modules/{$module}/controller.php")) {
$class = SugarAutoLoader::customClass(ucfirst($module).'Controller');
} else {
SugarAutoLoader::requireWithCustom('include/MVC/Controller/SugarController.php');
$class = SugarAutoLoader::customClass('SugarController');
}
if(class_exists($class, false)) {
$controller = new $class();
}
if(empty($controller)) {
$controller = new SugarController();
}
//setup the controller
$controller->setup($module);
return $controller;
}
}
比如,登录系统后,会进入到默认的Home模块(这个是在config.php中的'default_module'来获取的),那么依据上面的逻辑会加载SugarController类,因为在Home模块中没有controller.php文件,那么看看SugarController类的相关实现
/**
* ./include/MVC/Controller/SugarController.php
* 实例化&启动
*/
class SugarController
{
```
/**
* Constructor. This ie meant tot load up the module, action, record as well
* as the mapping arrays.
*/
public function SugarController(){
}
/**
* Called from SugarApplication and is meant to perform the setup operations
* on the controller.
* 模块的优先级是URL传过来的最高,其次是控制器工厂缓过来的
*/
public function setup($module = ''){
if(empty($module) && !empty($_REQUEST['module']))
$module = $_REQUEST['module'];
// set the module
if(!empty($module))
$this->setModule($module);
if(!empty($_REQUEST['target_module']) && $_REQUEST['target_module'] != 'undefined') {
$this->target_module = $_REQUEST['target_module'];
}
// set properties on the controller from the $_REQUEST
// 依据URL传过来的值设置方法、记录、试图、返回模块、返回方法、返回id
$this->loadPropertiesFromRequest();
// load the mapping files
$this->loadMappings();
}
/**
* Set the module on the Controller
*
* @param object $module
*/
public function setModule($module){
$this->module = $module;
}
/**
* Set properties on the Controller from the $_REQUEST
*
*/
private function loadPropertiesFromRequest(){
if(!empty($_REQUEST['action']))
$this->action = $_REQUEST['action'];
if(!empty($_REQUEST['record']))
$this->record = $_REQUEST['record'];
if(!empty($_REQUEST['view']))
$this->view = $_REQUEST['view'];
if(!empty($_REQUEST['return_module']))
$this->return_module = $_REQUEST['return_module'];
if(!empty($_REQUEST['return_action']))
$this->return_action = $_REQUEST['return_action'];
if(!empty($_REQUEST['return_id']))
$this->return_id = $_REQUEST['return_id'];
}
/**
* Load map files for use within the Controller
*
*/
private function loadMappings(){
$this->loadMapping('action_view_map');
$this->loadMapping('action_file_map');
$this->loadMapping('action_remap', true);
}
/**
* Generic load method to load mapping arrays.
* 若include/MVC/Controller/{$var}.php和modules/{$this->module}/{$var}.php两者存在[之一]
* 则加载并把其值存储到缓存和本类相关属性中
*/
private function loadMapping($var, $merge = false){
$$var = sugar_cache_retrieve("CONTROLLER_". $var . "_".$this->module);
if(!$$var){
if($merge && !empty($this->$var)){
$$var = $this->$var;
}else{
$$var = array();
}
foreach(SugarAutoLoader::existingCustom("include/MVC/Controller/{$var}.php", "modules/{$this->module}/{$var}.php") as $file) {
require $file;
}
$varname = str_replace(" ","",ucwords(str_replace("_"," ", $var)));
foreach(SugarAutoLoader::existing("custom/application/Ext/$varname/$var.ext.php", "custom/modules/{$this->module}/Ext/$varname/$var.ext.php") as $file) {
require $file;
}
sugar_cache_put("CONTROLLER_". $var . "_".$this->module, $$var);
}
$this->$var = $$var;
}
```
}
这里主要看看loadMapping方法,这个主要是完成导入试图动作、后台操作、前台默认进入页面的相关逻辑,以下是依次打印的三个数组
$this->loadMapping('action_view_map');
format '<action_name>' => '<view_name>'
Array
(
[multieditview] => multiedit
[detailview] => detail
[editview] => edit
[listview] => list
[popup] => popup
[vcard] => vcard
[importvcard] => importvcard
[importvcardsave] => importvcardsave
[modulelistmenu] => modulelistmenu
[favorites] => favorites
[sidecar] => sidecar
[noaccess] => noaccess
[quickedit] => quickedit
[edit_mobile] => edit_mobile
[detail_mobile] => detail_mobile
[list_mobile] => list_mobile
[wirelessmodule] => wirelessmodule
[wirelessdetail] => wirelessdetail
[wirelesslist] => wirelesslist
[wirelessedit] => wirelessedit
[wlsave] => wirelesssave
[sugarpdf] => sugarpdf
[dc] => dc
[dcajax] => dcajax
[quick] => quick
[quickcreate] => quickcreate
[spot] => spot
[gs] => gs
[inlinefield] => inlinefield
[inlinefieldsave] => inlinefieldsave
[pluginlist] => plugins
[downloadplugin] => downloadplugin
[metadata] => metadata
[cubes] => cubes
[debug] => debug
[additionaldetailsretrieve] => additionaldetailsretrieve
[tour] => tour
)
$this->loadMapping('action_file_map');
页面相关操作涉及到文件
Array
(
[subpanelviewer] => include/SubPanel/SubPanelViewer.php
[save2] => include/generic/Save2.php
[targetlistupdate] => modules/ProspectLists/TargetListUpdate.php
[deleterelationship] => include/generic/DeleteRelationship.php
[import] => modules/Import/index.php
[viewsugarfieldcollection] => include/SugarFields/Fields/Collection/view.sugarfieldcollection.php
)
$this->loadMapping('action_remap', true);
存放的是点击模块后默认进入的就是列表页
Array
(
[index] => listview
)
上面已经实例化了具体控制类,下面便来看看“$this->controller->execute();”的逻辑
class SugarController
{
```
/**
* This method is called from SugarApplication->execute and it will bootstrap the entire controller process
*/
final public function execute()
{
try
{
$this->process();
// 为空
$this->postProcess();
if(!empty($this->view))
{
// 此方法可以看看ViewFactory一章
$this->processView();
}
elseif(!empty($this->redirect_url))
{
$this->redirect();
}
}
catch (Exception $e)
{
$this->handleException($e);
}
}
/**
* if we have a function to support the action use it otherwise use the default action
*
* 1) check for file
* 2) check for action
*/
public function process(){
// sidecar,这一步是在SugarApplication中$this->controller->action = 'sidecar';设置的
$GLOBALS['action'] = $this->action;
// Home
$GLOBALS['module'] = $this->module;
// check to ensure we have access to the module.
// 类属性默认为true
if($this->hasAccess){
// sidecar
$this->do_action = $this->action;
/*
* sidecar
public static $action_case_file = array(
'editview'=>'EditView',
'detailview'=>'DetailView',
'listview'=>'ListView'
);
public static function getActionFilename($action) {
if(isset(self::$action_case_file[$action])) {
return self::$action_case_file[$action];
}
return $action;
}
*/
$file = self::getActionFilename($this->do_action);
/*
* 由于Home的newBean为null,因此这一步没啥用
public function loadBean()
{
$bean = BeanFactory::newBean($this->module);
if(!empty($bean)) {
$this->bean = $bean;
if(!empty($this->record)){
$this->bean->retrieve($this->record);
if(!empty($this->bean->id)) {
$GLOBALS['FOCUS'] = $this->bean;
}
}
}
}
*/
$this->loadBean();
/*
* 任务列表,依次执行以下方法,只要有一个方法中$this->_processed为true,那么便结束
public $process_tasks = array(
'blockFileAccess',
'handleEntryPoint',
'remapWirelessAction',
'callLegacyCode',
'remapAction',
'handle_action',
'handleActionMaps',
);
*/
if (!$this->_processed) {
foreach ($this->process_tasks as $process) {
$this->$process();
if ($this->_processed) {
break;
}
}
}
$this->redirect();
}else{
$this->no_access();
}
}
// 这一步不会执行,因为在配置文件中admin_access_control为false
// 若是要执行,需设为true
private function blockFileAccess(){
// check if the we have enabled file_access_control and if so then check the mappings on the request;
if(!empty($GLOBALS['sugar_config']['admin_access_control']) && $GLOBALS['sugar_config']['admin_access_control']){
$this->loadMapping('file_access_control_map');
//since we have this turned on, check the mapping file
$module = strtolower($this->module);
$action = strtolower($this->do_action);
if(!empty($this->file_access_control_map['modules'][$module]['links'])){
$GLOBALS['admin_access_control_links'] = $this->file_access_control_map['modules'][$module]['links'];
}
if(!empty($this->file_access_control_map['modules'][$module]['actions']) && (in_array($action, $this->file_access_control_map['modules'][$module]['actions']) || !empty($this->file_access_control_map['modules'][$module]['actions'][$action]))){
// check params
if(!empty($this->file_access_control_map['modules'][$module]['actions'][$action]['params'])){
$block = true;
$params = $this->file_access_control_map['modules'][$module]['actions'][$action]['params'];
foreach($params as $param => $paramVals){
if(!empty($_REQUEST[$param])){
if(!in_array($_REQUEST[$param], $paramVals)){
$block = false;
break;
}
}
}
if($block){
$this->_processed = true;
$this->no_access();
}
}else{
$this->_processed = true;
$this->no_access();
}
}
}else
$this->_processed = false;
}
/**
* 统一入口
* This code is part of the entry points reworking. We have consolidated all
* entry points to go through index.php. Now in order to bring up an entry point
* it will follow the format:
* 'index.php?entryPoint=download'
* the download entry point is mapped in the following file: entry_point_registry.php
*
*/
private function handleEntryPoint(){
if(!empty($_REQUEST['entryPoint'])){
$this->loadMapping('entry_point_registry');
$entryPoint = $_REQUEST['entryPoint'];
if(!empty($this->entry_point_registry[$entryPoint])){
require_once($this->entry_point_registry[$entryPoint]['file']);
$this->_processed = true;
$this->view = '';
}
}
}
/**
* 这个主要是从手机端来的
* Remap the action to the wireless equivalent if the module supports it and
* the user is on a mobile device. Also, map wireless actions to normal ones if the
* user is not using a wireless device.
*/
private function remapWirelessAction()
{
$this->loadMapping('wireless_module_registry');
$this->view_object_map['wireless_module_registry'] = $this->wireless_module_registry;
$action = strtolower($this->action);
if ($this->module == 'Home' && $this->action == 'index' && isset($_SESSION['isMobile'])) {
header('Location:index.php?module=Users&action=wirelessmain&mobile=1');
}
elseif(isset($this->wireless_module_registry[$this->module]) && isset($_SESSION['isMobile'])){
if ( $action == 'editview' ) $this->action = 'wirelessedit';
if ( $action == 'detailview' ) $this->action = 'wirelessdetail';
if ( $action == 'listview' ) $this->action = 'wirelesslist';
}
else {
if ( $action == 'wirelessedit' ) $this->action = 'editview';
if ( $action == 'wirelessdetail' ) $this->action = 'detailview';
if ( $action == 'wirelesslist' ) $this->action = 'listview';
if ( $action == 'wirelessmodule' ) $this->action = 'listview';
}
$this->do_action = $this->action;
}
/**
* Meant to handle old views e.g. DetailView.php.
* 兼容就的视图展示,可以通过此方法来二次开发
*/
protected function callLegacyCode()
{
$file = self::getActionFilename($this->do_action);
if ( isset($this->action_view_map[strtolower($this->do_action)]) ) {
$action = $this->action_view_map[strtolower($this->do_action)];
}
else {
$action = $this->do_action;
}
// index actions actually maps to the view.list.php view
if ( $action == 'index' ) {
$action = 'list';
}
$action = strtolower($action);
if((SugarAutoLoader::existing("modules/{$this->module}/{$file}.php") &&
!SugarAutoLoader::existing("modules/{$this->module}/views/view.{$action}.php")) ||
(SugarAutoLoader::existing("custom/modules/{$this->module}/{$file}.php") &&
!SugarAutoLoader::existing("custom/modules/{$this->module}/views/view.{$action}.php"))) {
// A 'classic' module, using the old pre-MVC display files
// We should now discard the bean we just obtained for tracking as the pre-MVC module will instantiate its own
unset($GLOBALS['FOCUS']);
$GLOBALS['log']->debug('Module:' . $this->module . ' using file: '. $file);
$this->action_default();
$this->_processed = true;
}
}
/**
* Actually remap the action if required.
*
*/
protected function remapAction(){
if(!empty($this->action_remap[$this->do_action])){
$this->action = $this->action_remap[$this->do_action];
$this->do_action = $this->action;
}
}
/**
* This array holds the methods that handleAction() will invoke, in sequence.
*/
protected $tasks = array(
'pre_action',
'do_action',
'post_action'
);
/**
* 在类中加载上述三个方法,如在实际操作中,需要在方法之前进行些工作,可以放在pre_action中 etc
* This method is called from the process method. I could also be called within an action_* method.
* It allows a developer to override any one of these methods contained within,
* or if the developer so chooses they can override the entire action_* method.
*
* @return true if any one of the pre_, do_, or post_ methods have been defined,
* false otherwise. This is important b/c if none of these methods exists, then we will run the
* action_default() method.
*/
protected function handle_action(){
$processed = false;
foreach($this->tasks as $task){
$processed = ($this->$task() || $processed);
}
$this->_processed = $processed;
}
/**
* 在上面的loadMapping中找执行的方法是否在里面,不在的话便拒绝执行
* 这一步时在process_task最后一个
* If the action has been remapped to a different action as defined in
* action_file_map.php or action_view_map.php load those maps here.
*
*/
private function handleActionMaps(){
if(!empty($this->action_file_map[strtolower($this->do_action)])){
$this->view = '';
$GLOBALS['log']->debug('Using Action File Map:' . $this->action_file_map[strtolower($this->do_action)]);
require_once($this->action_file_map[strtolower($this->do_action)]);
$this->_processed = true;
}elseif(!empty($this->action_view_map[strtolower($this->do_action)])){
$GLOBALS['log']->debug('Using Action View Map:' . $this->action_view_map[strtolower($this->do_action)]);
$this->view = $this->action_view_map[strtolower($this->do_action)];
$this->_processed = true;
}else
$this->no_action();
}
```
}