yii框架之mysql封装

        最近因采集项目中各服务日志信息,需要重写 mysql 基础类,故借此机会了解下。项目中常用 sql 语句大概有如下几种:

1: $res = Yii::$app->get('test')->createCommand($sql)->queryOne();
2: $res = Yii::$app->get('test')->createCommand()->update($table, $columns, $condition)->execute();
3: $res = Yii::$app->get('test')->createCommand()->BatchInsert($table, $columns, $rows)->execute();

        先看第一种,其中参数 test 是在 配置文件中定义的一个数据库配置:

'test' => array(
      'class' => 'yii\db\Connection',
      'dsn' => 'mysql:host=xx.xxx.xxx.xx;port=xx;dbname=test,
      'username' => root,
      'password' => '123456',
      'charset' => 'utf8mb4',
      'tablePrefix' => 'test_',
),

        由配置可见,经 yii 框架处理后,Yii::$app->get('test') 会生成一个 yii\db\Connection 类对象,继而调用成员方法 createCommand()。

public function createCommand($sql = null, $params = [])
{
    /** @var Command $command */
    $command = new $this->commandClass([
        'db' => $this,
        'sql' => $sql,
    ]);

    return $command->bindValues($params);
}

        在该方法中先是 new 一个 $this->commandClass 对象,然后再调用这个新对象的 bindValues() 方法。先看看这新对象是个啥:

public $commandClass = 'yii\db\Command';

class Command extends Component
{
    public $db;
    private $_sql;
}

class Component extends BaseObject;

class BaseObject implements Configurable
{
    public function __construct($config = [])
    {
        if (!empty($config)) {
            Yii::configure($this, $config);
        }
        $this->init();
    }
}

public static function configure($object, $properties)
{
    foreach ($properties as $name => $value) {
        $object->$name = $value;
    }

    return $object;
}

        该属性在定义时就已经声明成 yii\db\Command 类,所以在 createCommand() 方法生成的也是 Command 类对象。 参数是数组结构,由于自身没有构造函数,实际执行的是类 BaseObject 中的构造函数,最终在该构造函数中通过调用 BaseYii.php 文件中的 static 方法 configure() 给属性 $db$_sql 赋值。回到 Connect$commandClass 属性上来,余以为在定义时声明成外部类并不合理,会导致两个类之间的耦合比较严重,最好是通过构造方法进行外部注入,这样更合适点。然后调用 Command 类中的 bindValues() 方法:

// yii\\db\command
public function bindValues($values)
{
    ...
    $schema = $this->db->getSchema();
    ...
    return $this;
}

// yii\db\Connection
public function getSchema()
{
    ...
    $driver = $this->getDriverName();
    if (isset($this->schemaMap[$driver])) {
        $config = !is_array($this->schemaMap[$driver]) ? ['class' => $this->schemaMap[$driver]] : $this->schemaMap[$driver];
        $config['db'] = $this;

        return $this->_schema = Yii::createObject($config);
    }

    throw new NotSupportedException("Connection does not support reading schema information for '$driver' DBMS.");
}

public function getDriverName()
{
    if ($this->_driverName === null) {
        if (($pos = strpos($this->dsn, ':')) !== false) {
            $this->_driverName = strtolower(substr($this->dsn, 0, $pos));
        } else {
            $this->_driverName = strtolower($this->getSlavePdo()->getAttribute(PDO::ATTR_DRIVER_NAME));
        }
    }

    return $this->_driverName;
}

        该方法主要是通过解析变量 $dsn 来获取该库的 schema,然后返回类对象,回到一开始的应用层代码中,继续调用 queryOne() 方法。

public function queryOne($fetchMode = null)
{
    return $this->queryInternal('fetch', $fetchMode);
}

protected function queryInternal($method, $fetchMode = null)
{
    ...
    $this->prepare(true);
    ...
    $result = call_user_func_array([$this->pdoStatement, $method], (array) $fetchMode);
    ...
    return $result;
}

        可以看出调用的是 queryInternal() 方法,实际上所有的查询方法最终都是调用的该方法,只是参数不同。在 queryInternal() 方法中,继续调用 prepare() 方法。

public function prepare($forRead = null)
{
    ...
    $sql = $this->getSql();
    ...
    if ($forRead || $forRead === null && $this->db->getSchema()->isReadQuery($sql)) {
        $pdo = $this->db->getSlavePdo();
    } else {
        $pdo = $this->db->getMasterPdo();
    }
    ...
    $this->pdoStatement = $pdo->prepare($sql);
    $this->bindPendingParams();
}

        该方法主要是准备要执行的 sql 语句,并将其赋值给属性 $this->pdoStatement,最终执行回调函数 call_user_func_array() 获取查询结果。

        此时第一种情况已捋清楚,再看另外两种情况。后两种情况中, createCommand() 方法并没有 $sql 参数,所以在声明 yii\db\Command 类对象时,只有属性 $db 赋值, $_sql 此时还为空,这时就要看后面的 update() 和 batchInsert() 方法了。先看第二种情况:

public function update($table, $columns, $condition = '', $params = [])
{
    $sql = $this->db->getQueryBuilder()->update($table, $columns, $condition, $params);

    return $this->setSql($sql)->bindValues($params);
}

        方法体中第一句是获取完整的 sql 语句,然后调用 setSql() 方法给属性 $_sql 赋值:

public function setSql($sql)
{
    ...
    $this->_sql = $this->db->quoteSql($sql);

    return $this;
}

        可以看到是调用的 yii\db\Connection 类中的 quoteSql() 方法获取最终的 sql 语句。此处暂且不管 quoteSql() 方法是如何实现的,后面再说。实际上除了 BatchInsert() 方法,其他所有的修改操作调用的都是 setSql() 方法,那么 BatchInsert() 又是如何获取完整的 sql 语句的呢?

public function batchInsert($table, $columns, $rows)
{
    $table = $this->db->quoteSql($table);
    $columns = array_map(function ($column) {
        return $this->db->quoteSql($column);
    }, $columns);

    $sql = $this->db->getQueryBuilder()->batchInsert($table, $columns, $rows);

    $this->setRawSql($sql);

    return $this;
}

        方法体前面部分也是刚提到的 quoteSql() 方法,先放一边。在通过调用类 QueryBuilderBatchInsert() 方法生成完整的 sql 语句后,调用了 setRawSql() 方法。

public function setRawSql($sql)
{
    ...
    $this->_sql = $sql;

    return $this;
}

        这里是直接将参数 $sql 赋值给属性 $this->_sql,没调用 quoteSql() 方法很显然是因为在 BatchInsert() 方法中已经调用了。该看看 quoteSql() 方法体了。

public function quoteSql($sql)
{
    return preg_replace_callback(
        '/(\\{\\{(%?[\w\-\. ]+%?)\\}\\}|\\[\\[([\w\-\. ]+)\\]\\])/',
        function ($matches) {
            if (isset($matches[3])) {
                return $this->quoteColumnName($matches[3]);
            }

            return str_replace('%', $this->tablePrefix, $this->quoteTableName($matches[2]));
        },
        $sql
    );
}

        且不管 quoteColumnName() 和 quoteTableName() 俩方法后面还会调用几层方法,就 preg_replace_callback() 方法功能而言,是执行一个正则表达式搜索并且使用一个回调函数进行替换,此处是直接定义的匿名函数。最后是调用 execute() 方法, 和 queryInternal() 方法功能大体差不多。

public function execute()
{
    ...
    $this->prepare(false);
    ...
    $this->pdoStatement->execute();
    $n = $this->pdoStatement->rowCount();

    return $n;
}

        至此,yii 框架对 mysql 的封装已基本理清楚。注意的是,考虑到正则匹配效率相对较低,实际项目中应尽量避免。在有关数据库的任何操作时,最好是利用各参数拼接成完整的 sql 语句,作为方法 createCommand() 的参数赋值给属性 $_sql

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值