同福

做个web框架(8)——模型Model(二)TFDO对象的设计【20201101】

介绍

介绍

今天我们来完成数据库操作对象TFDO的设计和实现,TFDO是基于PDO扩展开发的,所以我们需要在环境里面配置PDO扩展,使用TFLinux的童鞋们就省去了这个步骤,因为福哥已经带着大家配置了PDO扩展了。

有的童鞋可能会有疑问,既然PDO可以实现对数据库的操作,我们为什么不直接使用PDO对象而非要基于它封装一个TFDO对象呢?难道只是为了换个对象名称吗?当然不是为了换个名字这个目的了,这里面有个很重要的考量,如果我们直接使用PDO对象会有如下几方面的问题:

  • 如果PDO对象升级了,更新了函数参数,甚至改变使用方法,那么会造成所有用到PDO对象的代码全部需要整理一遍,这个工作量忒大了点。

  • 如果PDO对象哪天停止维护了,需要替换新的扩展或者新的解决方案,同样会影响所有调用PDO对象的代码,这是在太恐怖了。

  • PDO对象本身的函数不见得会适合我们的开发环境,为了项目开发便利,我们需要对PDO原生函数进行一些加工改造。

PDO

首先我们先来了解一下PDO对象的使用方法,我们的TFLinux里面安装的数据库是MySQL,我们用它来测试PDO对象的使用。

连接数据库

首先我们需要对PDO对象进行初始化,童鞋们看过前面的知识的都知道,我们曾经在“做个搜索引擎”的Python项目里面使用过MySQL数据库,那里面使用的账号是tfse,我们可以直接拿来测试使用。

$host = "127.0.0.1";
$user = "root";
$pwd = "abcdef";
$db  = "tfse";
$charset = "utf8";
try{
    // connect
    $mysqlObj = new \PDO("mysql:host=" . $host,
        $user,
        $pwd,
        array(
            \PDO::ATTR_TIMEOUT => 5,
        ));
    $mysqlObj->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);

    // use db
    $mysqlObj->exec("use `". $db. "`");

    // set charset
    $mysqlObj->exec("set names `". $charset. "`");
}
catch (\PDOException $e){
    var_dump($e);
}

读取数据

现在我们将websites表里的“tongfu.net”查询出来。

$host = "127.0.0.1";
$user = "root";
$pwd = "abcdef";
$db  = "tfse";
$charset = "utf8";
try{
    // connect
    $mysqlObj = new \PDO("mysql:host=" . $host,
        $user,
        $pwd,
        array(
            \PDO::ATTR_TIMEOUT => 5,
        ));
    $mysqlObj->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);

    // use db
    $mysqlObj->exec("use `". $db. "`");

    // set charset
    $mysqlObj->exec("set names `". $charset. "`");

    // query
    $sql = "select * from websites where domainName = :str";
    $rs = $mysqlObj->prepare($sql);
    $rs->bindValue(":str", "tongfu.net", PDO::PARAM_STR);
    $rs->execute();

    // fetch
    $row = $rs->fetch(\PDO::FETCH_ASSOC);
    var_dump($row);
}
catch (\PDOException $e){
    var_dump($e);
}

写入数据

现在我们通过PDO修改一下“tongfu.net”这行数据。

代码

$host = "127.0.0.1";
$user = "root";
$pwd = "abcdef";
$db  = "tfse";
$charset = "utf8";
try{
    // connect
    $mysqlObj = new \PDO("mysql:host=" . $host,
        $user,
        $pwd,
        array(
            \PDO::ATTR_TIMEOUT => 5,
        ));
    $mysqlObj->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);

    // use db
    $mysqlObj->exec("use `". $db. "`");

    // set charset
    $mysqlObj->exec("set names `". $charset. "`");

    // query
    $sql = "select * from websites where domainName = :str";
    $rs = $mysqlObj->prepare($sql);
    $rs->bindValue(":str", "tongfu.net", PDO::PARAM_STR);
    $rs->execute();

    // fetch
    $row = $rs->fetch(\PDO::FETCH_ASSOC);
    var_dump($row);

    // update
    $sql = "update websites set title = :str1 where domainName = :str2";
    $rs = $mysqlObj->prepare($sql);
    $rs->bindValue(":str1", "update by PDO");
    $rs->bindValue(":str2", "tongfu.net");
    $rs->execute();
}
catch (\PDOException $e){
    var_dump($e);
}

修改前

b6b87d9357402cbb.jpg

修改后

d71197fa86dfba09.jpg

TFDO

前面我们已经了解到了PDO原生方法的使用技巧,大家可以感觉到PDO的这些操作还是有点繁琐,针对这些我们进行一些封装。

TFDO对象我们放入了Database\SQL\TFDO路径下面,程序文件名称是TFDO.inc.php。使用TFDO需要在TFRouteMap.php里面包含进来。

构造器

public function __construct($driver, $host=null, $port=null, $user=null, $pwd=null, $db=null, $charset=null){
    $this->driver = $driver;
    $this->host = $host;
    $this->port = $port;
    $this->user = $user;
    $this->pwd = $pwd;
    $this->db = $db;
    $this->charset = $charset;

    $this->lastErrmsg = 0;
    $this->lastErrcode = "";
}

connect

public function connect(){
    switch($this->driver){
        case TFDO::T_MYSQL:
            if($this->host == null){
                $this->host = "localhost";
            }
            if($this->port == null){
                $this->port = 3306;
            }
            if($this->user == null){
                $this->user = "root";
            }
            if($this->pwd == null){
                $this->pwd = "";
            }
            if($this->db == null){
                $this->db = "test";
            }
            if($this->charset == null){
                $this->charset = "utf8";
            }

            try{
                // connect
                $this->PDO = new \PDO("mysql:host=". $this->host,
                    $this->user,
                    $this->pwd,
                    array(
                        \PDO::ATTR_TIMEOUT => 15,
                    ));
                $this->PDO->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);

                // use db
                $this->PDO->exec("use `". $this->db. "`");

                // set charset
                $this->PDO->exec("set names `". $this->charset. "`");
            }
            catch(\PDOException $e){
                throw $e;
            }
            break;
        default:
            throw new \Exception("invalid type of driver of TFDO", 10001001);
    }
}

fetchOne

public function fetchOne(string $sql, array $params=null):?array {
    try{
        // query
        $rs = $this->PDO->prepare($sql);
        if(is_array($params)){
            foreach($params as $param){
                $rs->bindValue($param['key'], $param['value'], $param['type']);
            }
        }
        $rs->execute();

        // fetch
        $row = $rs->fetch(\PDO::FETCH_ASSOC);

        return $row;
    }
    catch(\PDOException $e){
        $this->lastErrcode = $e->getCode();
        $this->lastErrmsg = $e->getMessage();
    }

    return null;
}

fetchAll

public function fetchAll(string $sql, array $params=null):?array {
    try{
        // query
        $rs = $this->PDO->prepare($sql);
        if(is_array($params)){
            foreach($params as $param){
                $rs->bindValue($param['key'], $param['value'], $param['type']);
            }
        }
        $rs->execute();

        // fetch
        $rows = array();
        while($row = $rs->fetch(\PDO::FETCH_ASSOC)){
            $rows[] = $row;
        }

        return $rows;
    }
    catch(\PDOException $e){
        $this->lastErrcode = $e->getCode();
        $this->lastErrmsg = $e->getMessage();
    }

    return null;
}

execute

public function execute(string $sql, array $params=null):bool {
    try{
        // execute
        $rs = $this->PDO->prepare($sql);
        if(is_array($params)){
            foreach($params as $param){
                $rs->bindValue($param['key'], $param['value'], $param['type']);
            }
        }
        $rs->execute();

        return true;
    }
    catch(\PDOException $e){
        $this->lastErrcode = $e->getCode();
        $this->lastErrmsg = $e->getMessage();
    }

    return false;
}

讲解

TFDO

现在我们来讲解一下TFDO对象。

构造器

在构造器里面我们将用户传入的参数保存到了TFDO的内部属性里面。

connect

在这个方法里面,我们通过内部属性初始化了PDO对象。同时我们还选择了数据库,还设置默认编码。

fetchOne

在这个方法里面,我们使用PDO对象进行了数据的读取操作,这个方法仅仅会将查询到的第一行数据提取出来。

fetchAll

在这个方法里面,我们使用PDO对象进行了数据的读取操作,这个方法会将查询到全部数据都提取出来放入一个二维数组里面。

execute

在这个方法里面,我们使用PDO对象进行了数据的写入操作,并返回了处理结果。

测试

现在我们使用TFDO来实现前面用PDO实现的功能。

$host = "127.0.0.1";
$user = "root";
$pwd = "abcdef";
$db  = "tfse";
$charset = "utf8";

// connect
$myTFDOObj = new TFDO(TFDO::T_MYSQL, $host, null, $user, $pwd, $db, $charset);
$myTFDOObj->connect();

// fetch one
$sql = "select * from websites where domainName = :str";
$row = $myTFDOObj->fetchOne($sql, array(
    array('key'=>":str", 'value'=>"tongfu.net", 'type'=>\PDO::PARAM_STR)
));
var_dump($row);

// fetch all
$sql = "select * from websites limit 3";
$rows = $myTFDOObj->fetchAll($sql);
var_dump($rows);

// update
$sql = "update websites set title = :str1 where domainName = :str2";
$result = $myTFDOObj->execute($sql, array(
    array('key'=>":str1", 'value'=>"update by PDO", 'type'=>\PDO::PARAM_STR),
    array('key'=>":str2", 'value'=>"tongfu.net", 'type'=>\PDO::PARAM_STR)
));
var_dump($result);

总结

今天我们实现了TFDO对象的设计,将复杂的PDO对象的操作进行了简化。最主要的是,我们可以不断地对这个TFDO对象进行优化、改进。

改进对象需要注意一些问题:

  • 不要改动现有方法的名称和参数

  • 不要改动现有对象的namespace路径

  • 不要改动现有对象的名称

我们可以添加新方法,这个新方法的功能可以和现有方法类似,但是绝对不能改掉现有的方法的名称或者参数,哪怕它是多么的不合理!

我们可以建立新对象,但是绝对不能改版对象的名称,无论它是多么不好看,甚至有错别字!

5. P.S.

微信公众号的文章发出去之后是不能编辑的,但是福哥偶尔会修复一些描述不到位、示例不正确、结构不清晰等等的文章错误,这些只能在网站上才能看到最新版本内容,望大家知晓~~