SugarCRM源码分析之数据库连接


        这里详细介绍下SugarCRM里的数据库连接相关逻辑,数据库的相关配置在config.php中的dbconfig和dbconfigoption数据项。

'dbconfig' => array ( 
    'db_host_name' => 'localhost', 
    'db_host_instance' => 'SQLEXPRESS', 
    'db_user_name' => 'root', 
    'db_password' => '123456', 
    'db_name' => 'sugarcrm', 
    'db_type' => 'mysql', 
    'db_port' => '', 

    // 这个配置项,主要是选择当前的数据库连接类型
    // ./include/database可以查看
    'db_manager' => 'MysqliManager', 
), 
'dbconfigoption' => array ( 
    'persistent' => false, 
    'autofree' => false, 
    'debug' => 0, 
    'ssl' => false, 
    'collation' => 'utf8_general_ci', 
)

        接下来便分析下数据库连接过程

// ./include/entryPoint.php
require_once('include/database/DBManagerFactory.php');
require_once('data/SugarBean.php');

// ./include/entryPoint.php
// 获取数据库实例
$db = DBManagerFactory::getInstance();

// 重置数据库连接次数
$db->resetQueryCount();
/*
    ./include/database/DBManager.php
    public static function resetQueryCount() {
        self::$queryCount = 0;
    }
*/

// ./include/database/DBManagerFactory.php'
public static function getInstance($instanceName = '') {
    global $sugar_config;
    static $count = 0, $old_count = 0;
    /*
        'dbconfig' => array (
            'db_host_name' => 'localhost',
            'db_host_instance' => 'SQLEXPRESS',
            'db_user_name' => 'root',
            'db_password' => '123456',
            'db_name' => 'sugarcrm',
            'db_type' => 'mysql',
            'db_port' => '',
            'db_manager' => 'MysqliManager',
        ),
        'dbconfigoption' => array (
            'persistent' => false,
            'autofree' => false,
            'debug' => 0,
            'ssl' => false,
            'collation' => 'utf8_general_ci',
        )
    */
    if (empty($sugar_config['dbconfig'])) {
        return false;
    }
    // fall back to the default instance name
    if (empty($sugar_config['db'][$instanceName])) {
        $instanceName = '';
    }
    if (!isset(self::$instances[$instanceName])) {
        $config = $sugar_config['dbconfig'];
        $count++;

        // 返回具体数据库操作类
        self::$instances[$instanceName] = self::getTypeInstance($config['db_type'], $config);

        // 设置数据库连接基本信息,主要是字符编码统一为utf8_general_ci
        if (!empty($sugar_config['dbconfigoption'])) {
            self::$instances[$instanceName]->setOptions($sugar_config['dbconfigoption']);
        }

        // 连接数据库,并设置此连接相关属性
        self::$instances[$instanceName]->connect($config, true);
        self::$instances[$instanceName]->count_id = $count;
        self::$instances[$instanceName]->references = 0;

        // $GLOBALS['db']设置为当前的数据库连接
        if (empty($instanceName) && empty($GLOBALS['db'])) {
            $GLOBALS['db'] = self::$instances[$instanceName];
        }

        // 在./include/entryPoint.php中$system_config = Administration::getSettings();
        if (empty($instanceName) && !empty($GLOBALS['system_config']) && $GLOBALS['system_config'] instanceof Administration && empty($GLOBALS['system_config']->db)) {
            $GLOBALS['system_config']->db = self::$instances[$instanceName];
        }
    } else {
        $old_count++;
        self::$instances[$instanceName]->references = $old_count;
    }
    return self::$instances[$instanceName];
}

// 获取数据库类型实例
/*
    $type = 'mysql';
    $config = array (
            'db_host_name' => 'localhost',
            'db_host_instance' => 'SQLEXPRESS',
            'db_user_name' => 'root',
            'db_password' => '123456',
            'db_name' => 'sugarcrm',
            'db_type' => 'mysql',
            'db_port' => '',
            'db_manager' => 'MysqliManager',
        );
*/
public static function getTypeInstance($type, $config = array()) {
    global $sugar_config;

    // MysqliManager
    if (empty($config['db_manager'])) {
        // standard types
        switch ($type) {
            case "mysql":
                if (empty($sugar_config['mysqli_disabled']) && function_exists('mysqli_connect')) {
                    $my_db_manager = 'MysqliManager';
                } else {
                    $my_db_manager = "MysqlManager";
                }
                break;
            case "mssql":
                if (function_exists('sqlsrv_connect') && (empty($config['db_mssql_force_driver']) || $config['db_mssql_force_driver'] == 'sqlsrv' )) {
                    $my_db_manager = 'SqlsrvManager';
                } elseif (self::isFreeTDS() && (empty($config['db_mssql_force_driver']) || $config['db_mssql_force_driver'] == 'freetds' )) {
                    $my_db_manager = 'FreeTDSManager';
                } else {
                    $my_db_manager = 'MssqlManager';
                }
                break;
            default:
                $my_db_manager = self::getManagerByType($type, false);
                if (empty($my_db_manager)) {
                    echo $type . "\n";
                    display_stack_trace();
                    $GLOBALS['log']->fatal("unable to load DB manager for: $type");
                    sugar_die("Cannot load DB manager");
                }
        }
    } else {
        $my_db_manager = $config['db_manager'];
    }

    // sanitize the name
    // 过滤非法字符
    $my_db_manager = preg_replace("/[^A-Za-z0-9_-]/", "", $my_db_manager);

    // 从配置中获取db_manager_class,默认为空
    // 走MysqliManager数据库操作逻辑
    // include/database/MysqliManager.php
    if (!empty($config['db_manager_class'])) {
        $my_db_manager = $config['db_manager_class'];
    } else {
        SugarAutoLoader::requireWithCustom("include/database/{$my_db_manager}.php");
    }

    // 实例化MysqliManager类
    if (class_exists($my_db_manager)) {
        return new $my_db_manager();
    } else {
        return null;
    }
}

./include/database/MysqliManager.php
// MysqlManager又继承了DBManager
class MysqliManager extends MysqlManager {

    public function __construct() {

        // 执行的是DBManager的__construct方法
        parent::__construct();
        $this->capabilities["recursive_query"] = true;
        $this->capabilities["prepared_statements"] = true;
    }
}

./include/database/DBManager.php
abstract class DBManager {
    public function __construct() {

        // 设置时区,entryPoint.php引入的require_once('include/TimeDate.php');
        $this->timedate = TimeDate::getInstance();

        // $GLOBALS['log']日志操作类
        $this->log = $GLOBALS['log'];

        // compatibility,看了DBHelper.php介绍都移到本类了
        $this->helper = $this;

        // 若是从index.php->entryPoint.php过来的话,ENTRY_POINT_TYPE为gui
        if (defined('ENTRY_POINT_TYPE') && constant('ENTRY_POINT_TYPE') == 'api') {
            $this->encode = false;
        }
    }
}


        接着着重分析下connect过程,相信看完之后一定有收获

/**
 * @see DBManager::connect()
 */
public function connect(array $configOptions = null, $dieOnError = false) {

    // 这两步应该是兼容写法,当直接连接时,可以不用传参数,只需在配置文件里配置好
    // 数据库信息
    global $sugar_config;
    if (is_null($configOptions))
        $configOptions = $sugar_config['dbconfig'];

    // 首次连接肯定没有
    if (!isset($this->database)) {

        //mysqli connector has a separate parameter for port.. We need to separate it out from the host name
        // 这一步可以看出,db_host_name配置可以这么写localhost:3306
        $dbhost = $configOptions['db_host_name'];
        $dbport = null;
        $pos = strpos($configOptions['db_host_name'], ':');
        if ($pos !== false) {
            $dbhost = substr($configOptions['db_host_name'], 0, $pos);
            $dbport = substr($configOptions['db_host_name'], $pos + 1);
        }

        // 是否持久化连接
        if (ini_get('mysqli.allow_persistent') && $this->getOption('persistent')) {
            $dbhost = "p:" . $dbhost;
        }

        // 连接数据库,并放入$this->database中,下次做数据库操作时
        // 可以判断下此变量
        $this->database = mysqli_connect($dbhost, $configOptions['db_user_name'], $configOptions['db_password'], isset($configOptions['db_name']) ? $configOptions['db_name'] : '', $dbport);

        // 数据库连接失败,必须返回错误,并停止程序执行
        if (empty($this->database)) {
            $GLOBALS['log']->fatal("Could not connect to DB server " . $dbhost . " as " . $configOptions['db_user_name'] . ". port " . $dbport . ": " . mysqli_connect_error());
            if ($dieOnError) {

                // $GLOBALS['app_strings']存放的是语言包数组
                if (isset($GLOBALS['app_strings']['ERR_NO_DB'])) {
                    sugar_die($GLOBALS['app_strings']['ERR_NO_DB']);
                } else {
                    sugar_die("Could not connect to the database. Please refer to sugarcrm.log for details.");
                }
            } else {
                return false;
            }
        }
    }

    // 如果连接数据库成功,但是没有数据库认为报错并停止程序执行
    if (!empty($configOptions['db_name']) && !@mysqli_select_db($this->database, $configOptions['db_name'])) {
        $GLOBALS['log']->fatal("Unable to select database {$configOptions['db_name']}: " . mysqli_connect_error());
        if ($dieOnError) {
            if (isset($GLOBALS['app_strings']['ERR_NO_DB'])) {
                sugar_die($GLOBALS['app_strings']['ERR_NO_DB']);
            } else {
                sugar_die("Could not connect to the database. Please refer to sugarcrm.log for details.");
            }
        } else {
            return false;
        }
    }

    // cn: using direct calls to prevent this from spamming the Logs
    // 设置字符集
    mysqli_query($this->database, "SET CHARACTER SET utf8");
    $names = "SET NAMES 'utf8'";

    // 这一步是从self::$instances[$instanceName]->setOptions($sugar_config['dbconfigoption']); 
    // 获取数据的,也就是调用connect的上一步
    $collation = $this->getOption('collation');
    if (!empty($collation)) {
        $names .= " COLLATE '$collation'";
    }
    mysqli_query($this->database, $names);

    if ($this->checkError('Could Not Connect', $dieOnError))
        $GLOBALS['log']->info("connected to db");

    $this->connectOptions = $configOptions;
    return true;
}

./include/database/DBManager.php
// 获取配置
public function getOption($option) {
    if (isset($this->options[$option])) {
        return $this->options[$option];
    }
    return null;
}

// 核对错误,主要是判断数据库是否连接,最后一次数据库操作是否有误
public function checkError($msg = '', $dieOnError = false) {
    if (empty($this->database)) {
        $this->registerError($msg, "Database Is Not Connected", $dieOnError);
        return true;
    }

    $dberror = $this->lastDbError();
    if ($dberror === false) {
        $this->last_error = false;
        return false;
    }
    $this->registerError($msg, $dberror, $dieOnError);
    return true;
}

// 如果$message不为空,说明最后一次执行sql有误
// 这时再判断$dieOnError是否要终止执行程序
public function registerError($userMessage, $message, $dieOnError = false) {
    if (!empty($message)) {
        if (!empty($userMessage)) {
            $message = "$userMessage: $message";
        }
        if (empty($message)) {
            $message = "Database error";
        }
        $this->log->fatal($message);
        if ($dieOnError || $this->dieOnError) {
            if (isset($GLOBALS['app_strings']['ERR_DB_FAIL'])) {
                sugar_die($GLOBALS['app_strings']['ERR_DB_FAIL']);
            } else {
                sugar_die("Database error. Please check sugarcrm.log for details.");
            }
        } else {
            $this->last_error = $message;
        }
    }
}


        看完后,才发现配置文件中的db_port竟然是虚设的,而是在db_host中这样'localhost:3306'配置。是否有很大收获啊,否则 一旦数据库端口不是3306,而又在db_port中配置了端口,结果是死活连接不了数据库,结果废了大半天劲看源码发现问题竟然出现这里。


        好了,数据库也连接上了,那么就一口气看看数据库查询的分析吧。

$q = "SELECT id, name, symbol, conversion_rate FROM currencies WHERE status = 'Active' and deleted = 0";
$r = $db->query($q);

./include/database/MysqliManager.php
public function query($sql, $dieOnError = false, $msg = '', $suppress = false, $keepResult = false) {
    $result = $this->queryMulti($sql, $dieOnError, $msg, $suppress, $keepResult, false);
    return $result;
}

protected function queryMulti($sql, $dieOnError = false, $msg = '', $suppress = false, $keepResult = false, $multiquery = true) {
    if (is_array($sql)) {

        // queryArray does not support any return sets
        // 其实就是批量执行
        /*
        public function queryArray(array $sqls, $dieOnError = false, $msg = '', $suppress = false) {
            $last = true;
            foreach ($sqls as $sql) {
                if (!($last = $this->query($sql, $dieOnError, $msg, $suppress))) {
                    break;
                }
            }
            return $last;
        }
        */
        return $this->queryArray($sql, $dieOnError, $msg, $suppress);
    }
    static $queryMD5 = array();

    // 此两步不执行
    parent::countQuery($sql);
    $GLOBALS['log']->info('Query:' . $sql);

    /*
     * 主要是看看数据库是否连接
     * 刚刚分析过,数据库后回话信息保持在$this->database中
     * 如果没有则重新连接,出错了,那么久终止执行
    public function checkConnection() {
        $this->last_error = '';
        if (!isset($this->database))
            $this->connect();
    }
    */
    $this->checkConnection();

    // 记录sql开始执行时间
    $this->query_time = microtime(true);

    // 记录执行sql
    $this->lastsql = $sql;

    // 默认执行多条sql语句查询
    if ($multiquery) {
        $query_result = $suppress ? @mysqli_multi_query($this->database, $sql) : mysqli_multi_query($this->database, $sql);
        $result = mysqli_use_result($this->database);

        // Clear any remaining recordsets
        while (mysqli_next_result($this->database)) {
            $tmp_result = mysqli_use_result($this->database);
            mysqli_free_result($tmp_result);
        }
    } else
        $result = $suppress ? @mysqli_query($this->database, $sql) : mysqli_query($this->database, $sql);

    // 这两步感觉很鸡肋,因为只保存了,但是没有哪个地方用到
    $md5 = md5($sql);
    if (empty($queryMD5[$md5]))
        $queryMD5[$md5] = true;

    // 记录sql查询时间
    $this->query_time = microtime(true) - $this->query_time;
    $GLOBALS['log']->info('Query Execution Time:' . $this->query_time);

    // slow query logging
    // 如果执行的sql超过了配置文件中的数据,那么便记录下来
    // 前提是配种文件中陪了该项,否则不执行
    if ($this->dump_slow_queries($sql)) {
        $this->track_slow_queries($sql);
    }

    // This is some heavy duty debugging, leave commented out unless you need this:
    /*
      $bt = debug_backtrace();
      $line['file'] = 'NO_FILE';
      $line['line'] = 'NO_LINE';
      $line['function'] = 'NO_FUNCTION';
      $i = 0;
      foreach ( $bt as $i => $tryLine ) {
      if ( strpos($tryLine['file'],'include/database') === false && strpos($tryLine['file'],'include/SugarQuery') === false ) {
      $line = $tryLine;
      // Go function line up to find the real function
      if ( isset($bt[($i+1)]['function']) ) {
      $line['function'] = $bt[($i+1)]['function'];
      }
      break;
      }
      }
      $dumpQuery = str_replace(array('      ','     ','    ','   ','  ',"\n","\t","\r"),
      array(' ',     ' ',    ' ',   ' ',  ' ', ' ', ' ', ' ',),
      $sql);

      $GLOBALS['log']->fatal("{$line['file']}:{$line['line']} ${line['function']} \nQuery: $dumpQuery\n");
     */

    // 默认为false不执行
    if ($keepResult) {
        $this->lastResult = $result;
    }

    // 若数据库挂了便写条报警并重新再连接
    if ($this->database && mysqli_errno($this->database) == 2006 && $this->retryCount < 1) {
        $GLOBALS['log']->fatal('mysqli has gone away, retrying');
        $this->retryCount++;
        $this->disconnect();
        $this->connect();
        return $this->query($sql, $dieOnError, $msg, $suppress, $keepResult);
    } else {
        $this->retryCount = 0;
    }

    // 查看sql是否有误
    $this->checkError($msg . ' Query Failed: ' . $sql, $dieOnError);

    // 没有的话便返回查询结果
    return $result;
}

./incldue/database/DBManager.php
// 如果执行的sql超过了配置文件中的数据,那么便记录下来
public function dump_slow_queries($query) {
    global $sugar_config;

    $do_the_dump = isset($sugar_config['dump_slow_queries']) ? $sugar_config['dump_slow_queries'] : false;
    $slow_query_time_msec = isset($sugar_config['slow_query_time_msec']) ? $sugar_config['slow_query_time_msec'] : 5000;

    if ($do_the_dump) {
        if ($slow_query_time_msec < ($this->query_time * 1000)) {
            // Then log both the query and the query time
            $this->log->fatal('Slow Query (time:' . $this->query_time . "\n" . $query);
            return true;
        }
    }
    return false;
}

        最后看看针对select查询的处理

while($a = $db->fetchByAssoc($r)) {
    $load = array();
    $load['name'] = $a['name'];
    $load['symbol'] = $a['symbol'];
    $load['conversion_rate'] = $a['conversion_rate'];

    $this->currencies[$a['id']] = $load;
}
./include/database/DBManager.php
public function fetchByAssoc($result, $encode = true) {
    if (empty($result))
        return false;

    // 这是针对老旧版本的api来了,不用看
    if (is_int($encode) && func_num_args() == 3) {
        // old API: $result, $rowNum, $encode
        $GLOBALS['log']->deprecated("Using row number in fetchByAssoc is not portable and no longer supported. Please fix your code.");
        $encode = func_get_arg(2);
    }

    if ($result instanceof PreparedStatement) {
        $row = $result->preparedStatementFetch();
    } else {

        /*
         * 这里就是简单的调用mysqli_fetch_assoc处理
        public function fetchRow($result) {
            if (empty($result))
                return false;

            $row = mysqli_fetch_assoc($result);
            if ($row == null)
                $row = false; //Make sure MySQLi driver results are consistent with other database drivers
            return $row;
        }
        */
        $row = $this->fetchRow($result);
    }

    // 如果设置了编码,则需转码
    /*
    public function encodeHTML($string) {
        if (empty($string) || !$this->encode) {
            return $string;
        }
        /** Not using ENT_HTML401|ENT_SUBSTITUTE since they are 5.4+ only */
        return htmlspecialchars($string, ENT_QUOTES, "UTF-8");
    }
    */
    if (!empty($row) && $encode && $this->encode) {
        return array_map(array($this, "encodeHTML"), $row);
    } else {
        return $row;
    }
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值