当前位置: 首页 > 后端技术 > PHP

写一个“特殊”的查询构造函数——(一.程序结构,基本封装)

时间:2023-03-29 20:24:46 PHP

程序结构本文开始,正式进入编码实践。首先简单规划一下程序的结构。上一篇文章提到,我们需要一个基类PDODriver来封装PDO的一些公共方法。每一个数据库如Mysql都会创建一个新的类(这里我们简称为驱动类),它继承自基类。为了开发的可扩展性和标准化,我们还需要一个接口ConnectorInterface,它声明了必须实现的方法,每个驱动类和基类都实现了这个接口。需要创建的源码文件如下:项目根目录/ConnectorInterface.php--接口文件PDODriver.php--基类Mysql.php--Mysql驱动类Pgsql.php--PostgreSql驱动类Sqlite.php--Sqlitedriverclassbase类的创建,首先启动PDO连接的是基类,它封装了PDO连接。要创建新的PDO连接,应将初始化参数(dsn、用户名、密码、选项)提供给构造函数。PDO连接不暴露在外部,隐藏了pdo的内部使用。为了便于灵活控制和独立的连接和断开方式,一个基本的基类PDODriver.php代码如下://声明一个命名空间,方便代码管理namespaceDrivers;//PDO类使用绝对命名空间usePDO;usePDOException;//声明基类classPDODriverimplementsConnectorInterface{//用于保存PDO连接,内部使用和派生类,隐藏外部保护$_pdo=NULL;//保存初始化参数protected$_config=[];//PDOoptions信息,在这里配置一些基本设置/如果发生错误,则抛出PDOException异常PDO::ATTR_ORACLE_NULLS=>PDO::NULL_NATURAL,//SQL中获取数据时将空字符串转换为NULLPDO::ATTR_STRINGIFY_FETCHES=>FALSE,//禁止将值转换为stringsPDO::ATTR_EMULATE_PREPARES=>FALSE,//禁用准备语句的模拟];//构造函数,保存初始化参数,创建连接publicfunction__construct($config){$this->_config=$config;$this->_connect();}//创建连接,这里以mysql为例protectedfunction_connect(){//将初始化参数数组构造成一个单独的变量提取物($this->_config,EXTR_SKIP);//构建dsn$dsn='mysql:dbname='.$dbname.';host='.$host.';port='.$port;//构建选项$options=isset($options)?$options+$this->_options:$this->_options;try{//创建pdo连接$this->_pdo=newPDO($dsn,$user,$password,$options);}catch(PDOException$e){//如果失败,抛出异常throw$e;}}//关闭连接保护函数_closeConnection(){$this->_pdo=NULL;}}基类实现接口ConnectorInterface接口的代码如下:namespaceDrivers;interfaceConnectorInterface{//使用接口指定必须实现的公共方法publicfunction__construct($config);}之后,每次你在基类中添加了一个public方法,它会在接口中声明,以后不会用到。在接口中声明方法的代码将单独描述。PDO的native方法的暴露虽然封装了PDO的操作,但是也有一些复杂的查询需要通过调用原有的接口来执行。因此,我们需要将PDO的基本prepare、exec、query暴露出来,提供更直接、更原始的接口。在基类PDODriver.php中添加方法:publicfunctionquery($sql){return$this->_pdo->query($sql);}publicfunctionexec($sql){return$this->_pdo->exec($sql);}publicfunctionprepare($sql,array$driver_options=[]){return$this->_pdo->prepare($sql,$driver_options);}自动加载目前是对PDO的简单封装完成,现在我们必须测试这个东西是否有效。但是,每次使用一个文件都需要,对于开发来说效率不是很高。如果能根据命名空间自动加载文件就更好了。如何实现?请来到作曲家和PSR-4。Composer是一个强大且易于使用的PHP依赖管理工具。此处不作详细说明。有关详细信息,请参阅相关文档。PSR-4是PHP的自动加载规范,composer已经支持PSR-4。OK,开始工作1.在任意目录下安装composer#curl-sShttps://getcomposer.org/installer|php#下载源文件并执行(注意composer下载需要翻墙)cpcomposer.phar/usr/local/bin/composer#将可执行文件放在已经设置环境变量的目录下2.在你的工程目录下新建一个src目录,将所有源码放在src/Drivers/下。在项目根目录运行composerinit,填写一些基本信息后,会在根目录生成一个composer.json文件,内容类似:{"name":"vagrant/query-builder","require":{}}修改composer.json,增加autoload字段,指定autoload规范为psr-4(这里我的命名空间是Drivers,所以设置为Drivers到src/Drivers目录的映射)。{"name":"vagrant/query-builder","require":{},"autoload":{"psr-4":{"Drivers\\":"src/Drivers/"}}}3.项目在该目录下运行composerinstall,生成vendor目录。现在只要导入vendor/autoload.php,就可以直接通过命名空间自动加载src/Drivers/下的文件了。在项目目录下新建test目录(方便对源码进行一些测试),在test目录下新建test.php用于测试基类(代码简单,此时没有使用phpunit)//引入composer的自动加载文件require_oncedirname(dirname(__FILE__))。'/vendor/autoload.php';//使用基类useDrivers\PDODriver;//使用你的数据库替换配置$config=['host'=>'localhost','port'=>'3306','user'=>'username','password'=>'password','dbname'=>'database',];$driver=newPDODriver($config);$results=$driver->query('select*fromyour_table');foreach($resultsas$result){var_dump($result);}4.运行根目录下的phptest/test.php,看看结果是否如你所料。此时的项目目录如下:项目根目录/src/Drivers/ConnectorInterface.phpPDODriver.phpMysql.phpPgsql。phpSqlite.phptest/test.phpvendor/...composer.jsonMysql驱动类的创建由于Mysql字符集、时区设置语句和其他数据库的不同,具有一些其他数据库没有的特性(unix_socket连接,严格模式等),因此,Mysql的驱动类具有基类的功能,但也有与基类不同的部分Mysql继承基类,实现了ConnectorInterface接口。mysql.php代码如下:namespaceDrivers;usePDO;usePDOException;useDrivers\PDODriver;classMysqlextendsPDODriverimplementsConnectorInterface{//重写基类protectedfunction的_connect方法_connect(){//解包配置数组提取($this->_config,EXTR_SKIP);//建立一个dsn来判断是否使用unix_socket创建一个mysql连接$dsn=isset($unix_socket)?'mysql:unix_socket='.$unix_socket.';dbname='.$dbname:'mysql:dbname='.$dbname.';host='.$host.(isset($port)?';port='.$端口:'');//选项options$options=isset($options)?$options+$this->_options:$this->_options;try{//创建连接$this->_pdo=newPDO($dsn,$user,$password,$options);//如果需要设置字符集->执行();}//如果需要设置时区if(isset($timezone)){$this->_pdo->prepare("settime_zone='$timezone'")->execute();}//如果需要的话设置严格模式}else{$this->_pdo->prepare("setsessionsql_mode=''")->execute();}}}catch(PDOException$e){//创建连接失败,抛出异常throw$e;}}}测试:修改测试.php//引入composer的自动加载文件require_oncedirname(dirname(__FILE__))。'/vendor/autoload.php';//使用Mysql驱动类useDrivers\Mysql;//使用你的数据库替换配置$config=['host'=>'localhost','port'=>'3306','user'=>'username','password'=>'password','dbname'=>'database','charset'=>'utf8','timezone'=>'+8:00','collection'=>'utf8_general_ci','strict'=>false,//'unix_socket'=>'/var/run/mysqld/mysqld.sock',];$驱动程序=新的Mysql($config);$results=$driver->query('select*fromyour_table');foreach($resultsas$result){var_dump($result);}PostgreSql,Sqlite驱动类创建PostgreSql,Sqlite驱动类的创建类似到Mysql驱动类的创建,这里就不赘述了,直接展示codePgsql.php代码如下:namespaceDrivers;usePDO;usePDOException;useDrivers\PDODriver;classPgsqlextendsPDODriverimplementsConnectorInterface{//ATTR_EMULATE_PREPARES常量不适用于postgresql,重写基类的_options属性protected$_options=[PDO::ATTR_CASE=>PDO::CASE_NATURAL,PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION,PDO::ATTR_ORACLE_NULLS=>PDO::NULL_NATURAL,PDO::ATTR_STRINGIFY_FETCHES=>false,];//连接方法覆盖受保护的函数_connect(){extract($this->_config,EXTR_SKIP);$dsn='pgsql:dbname='.$dbn嗯。(isset($host)?';host='.$host:'')。(isset($port)?';port='.$port:'')。(isset($sslmode)?';sslmode='.$sslmode:'');$options=isset($options)?$options+$this->_options:$this->_options;尝试{$this->_pdo=newPDO($dsn,$user,$password,$options);//字符集设置if(isset($charset)){$this->_pdo->prepare("setnames'$charset'")->execute();}//时区设置if(isset($timezone)){$this->_pdo->prepare("settimezone'$timezone'")->execute();}//postgresql模式路径设置if(isset($schema)){$this->_pdo->prepare("setsearch_pathto$schema")->execute();}//postgresql应用名称设置if(isset($application_name)){$this->_pdo->prepare("setapplication_nameto'$applicationName'")->execute();}}catch(PDOException$e){throw$e;}}}Sqlite是基于内存或者文件的,比较简单的Sqlite.php代码如下:namespaceDrivers;usePDO;usePDOException;useDrivers\PDODriver;classSqliteextendsPDODriverimplementsConnectorInterface{protectedfunction_connect(){extract($this->_config,EXTR_SKIP);//构建dsn//确定内存模式或文件模式if($dbname==':memory:'){$dsn='sqlite::memory:';}else{//获取db文件的绝对路径$path=realpath($dbname);if($path===FALSE){thrownewInvalidArgumentException("数据库$dbname不存在。");}$dsn='sqlite:'.$path;}$options=isset($options)?$options+$this->_options:$this->_options;try{//sqlite没有用户名密码$this->_pdo=newPDO($dsn,'','',$options);}catch(PDOException$e){扔$e;}}}PostgreSql和Sqlite的测试这里不再赘述。有兴趣的可以自己写个测试来了解一下其中的道理,那就来测试一下吧!