面向对象开发的规范
定义:面向对象规范是指我们在开发时,在技术没有强制规定的情况下,应该采用什么样的方式来协助我们进行代码的书写。
- 属性的初始化:属性是类对于同类事务所抽离出来的共性
数据
,本身在类结构中没有价值,是当具体对象产生之后,属于对象本身的。因此在进行类中定义属性的时候,通常不会对属性进行初始化,除非属性本身的值也具有共性
<?php
class Saler{
//属性
public $count;
//某个卖家拥有的商品数量,每位具体卖家对象拥有的不可能一样,所以没必要初始化
protected $discount;
//某个卖家针对销售的折扣,同样没有统一的价值
private $money = 0;
//某个卖家的账户余额,任何一位卖家一开始做生意的时候,账户余额都为0,所以可以初始化
?>
- 属性的初始化:属性在类中初始化的基本判定就是数据是否初始化的时候统一,如果数据统一(即初始化),那么所有对象在生成之后,就拥有对应的值;如果不初始化,意味着所有对象在生成后,应该对属性进行赋值。
<?php
class Saler{
//属性
public $count;
private $money = 0;
}
//实例化
$s1 = new Saler();
$s2 = new Saler(); //所有对象都有两个属性,其中$count都没有数据,而$money都为0
//生成对象后,对象对自己的属性进行初始化
$s1->count = 100;
$s2->count = 1000;
?>
- 访问修饰限定符选择:访问修饰限定符是用来限制类成员被对象访问时对象所处位置的。访问的权限从public、protected到private依次变小。使用规则如下:
- 设定好的类成员本身不会被外部用到,那么应该使用private或者protected
- 设定好的类成员一定会给外部访问,使用public
- 属性通常private居多,如果需要外部访问属性,通常会定义相关方法来实现属性的查看和修改,因为可以在方法内对数据逻辑进行代码控制,安全
- 总之一句话:尽可能增加类对成员的控制(尽可能多使用private,少使用public)
<?php
class Saler{
//属性
public $count;
private $money = 0;
//增加方法操作私有属性money
public function getMoney(){
return $this->money;
}
public function setMoney($money){
//可以对逻辑进行修改,对数据进行安全判定,保证数据的安全性
$this->money = $money;
//$this->money是属性,$money是外部传入的参数,二者同名但是性质完全不同
}
}
总结
- 属性在类中定义的时候,通常不会初始化值,除非所有类实例化得到的对象的某个属性需要是统一值
- 应该尽可能增加类对成员的控制,即使用范围较小的访问修饰限定符优先
- 属性通常是私有化的,一般是通过设定方法来实现属性的访问和修改
构造方法
定义:构造方法__construct(),是一种类结构特有的特殊方法,该方法由系统规定好,开发人员在定义的时候只需要写抄一遍,有了构造方法的类在实例化对象之后,对象就会自动调用。
- 构造方法实现:在类中增加一个方法__construct()即可
<?php
class Saler{
//属性
public $count;
private $money;
//构造方法
public function __construct(){
echo __CLASS__;
}
}
- 构造方法也是一个普通方法,不普通的地方在于,类实例化得到的对象会马上自动调用
<?php
//接上述代码
new Saler(); //输出Saler
- 构造方法的意义:构造方法是对象实例化的时候用来初始化对象的资源的,所以通常是用来初始化对象的属性或者其他资源初始化
<?php
class Saler{
//属性
public $count;
private $money;
//构造方法:初始化属性
public function __construct(){
$this->count = 100;
$this->money = 100;
}
}
- 如果属性的数据在构造方法中初始化是固定写死的,那么与直接在定义类的时候初始化属性一样。意味着数据没有任何价值(所有对象都相同),因此通常是通过构造方法的参数来实现数据的外部传入
<?php
class Saler{
//属性
public $count;
private $money;
//构造方法:初始化属性
public function __construct($count,$money){
$this->count = $count;
$this->money = $money;
}
}
- 一旦构造方法拥有了形参,那么对象在调用该方法的时候就需要传入对应的实参,而构造方法又是自动调用的,所以需要在实例化对象的时候使用new 类名(构造方法对应的实参列表)来实现
<?php
//接上述代码
$s1 = new Saler(100,100);
$s2 = new Saler(1000,1000);
注意:之前所说的new 类名
和new 类名()
没有区别是因为没有构造方法,或者构造方法没有参数限定,一旦构造方法有了参数,那么new 类名
就不能直接使用了。
- 构造方法不管再怎么特殊,也是用户定义的方法,言外之意除了在实例化对象时对象会自动调用之外,我们也可以手动调用构造方法(但是一般没有价值,因为对象实例化时会自动调用)
<?php
class Saler{
//属性
public $count;
private $money;
//构造方法:初始化属性
public function __construct($count,$money){
$this->count = $count;
$this->money = $money;
}
}
//实例化
$s = new Saler(100,100); //系统在new Saler(100,100)好之后,会自动调用一次
$s->__construct(1000,1000); //允许手动调用
总结
- 构造方法__construct()是一种系统内置的方法,该方法的特性是会在对象实例化之后,对象立即自动调用
- 构造方法的目的就是为了初始化资源,包含对象属性和其他资源
- 一旦构造方法定义好之后,且构造方法自带参数,那么就只能使用
new 类名(参数列表)
方式才能正确实例化 - 构造方法可以当做普通方法由对象调用(不建议)
析构方法
定义:析构方法__destruct(),也是一种类结构中特殊的方法,与构造方法一样,也是系统规定好,只需要开发人员一遍即可,对象在被销毁时会自动调用。
- 析构方法实现:类中增加一个__destruct()方法
<?php
class Saler{
//析构方法
public function __destruct(){
echo __FUNCTION__;
}
}
- 析构方法调用:析构方法是在对象被销毁时自动,对象的“垂死挣扎”
<?php
//接上述代码
//实例化
$s = new Saler();
unset($s); //删除对象
- 析构方法是对象用来调用释放对象中的资源,不是用来删除对象的
<?php
class Saler{
//析构方法
public function __destruct(){
//销毁对象所占用资源的代码
}
}
- 析构方法也是普通方法,可以由对象直接调用
<?php
//接析构方法实现代码
$s = new Saler();
$s->__destruct(); //思考:此时对象是否被销毁?
- 对象销毁情形
- 保存对象的变量被用来保存其他数据,导致对象内存没有任何变量引用
- 删除对象变量
- 脚本执行结束:释放所有变量
总结
- 析构方法是一种对象销毁时自动调用的方法
- 析构方法是用来对象销毁自身所占用的资源
- PHP中脚本执行结束,系统会自动回收所有资源,因此一般PHP中很少使用析构方法
对象传值
定义:对象传值,其实就是将保存对象的变量赋值给另外一个变量,在PHP中,对象的传值是引用传递的:即一个对象变量赋值给另外一个变量,两个变量指向同一个对象的内存地址,即只有一个对象。
- 对象传值就是保存对象的变量赋值给另外一个变量
<?php
class Saler{}
$s1 = new Saler();
$s2 = $s1;
- 对象传值是引用传递,不管对象赋值给多少个变量,内存中只有一个对象
<?php
class Saler{}
$s1 = new Saler();
$s2 = $s1;
//证明
var_dump($s1,$s2); //同一个对象
$s1->name = 'Saler'; //更改一个变量所保存对象的属性
echo $s2->name; //输出Saler
总结:对象传值是引用传值,一般情况下不会在项目中用到。
范围解析操作符
定义:范围解析操作符,由两个冒号组成“::”,是专门用于类实现类成员操作的,可以实现类直接访问类成员。
- 类常量的普通访问尝试:尝试使用对象进行访问
<?php
class Saler{
//类常量
const PI = 3.14;
}
$s1 = new Saler();
echo $s1->PI; //错误,$s1->PI最终转换的访问方式为:$PI,这个在类中并不存在
- 以上案例可以看出,对象无法访问类常量,那是因为类常量的定义本身就是用来给类访问的,对象是用来访问属性和方法的,类常量的访问方式为:类名::常量名
<?php
class Saler{
//类常量
const PI = 3.14;
}
echo Saler::PI; //输出3.14
- 分析:类常量是固定的,而对象的属性是不同对象而不同的,成员方法简单的理解也是为属性本身进行加工的。因此有一些东西是专属于类的,而有部分内容是专门为对象提供的,所以就会有不同的成员拥有不同的访问方式
总结
- 类访问成员的方式是使用范围解析操作符“::”访问,由类名直接访问:类名::类常量
- 类本身是通过对同类对象的抽象而形成,所以属性和方法本身都是由对象来访问
- 类也需要有一些自身的数据和操作,这些就由类来进行访问
静态成员
定义:静态成员,使用static关键字修饰的类成员,表示该成员属于类访问。PHP静态成员有两种,静态属性和静态方法。
- 静态属性:在类中定义属性的时候使用static关键字修饰,访问的时候只能使用类+范围解析操作符+静态属性访问
<?php
class Saler{
//属性
public $money = 0;
public static $count = 0; //静态属性
}
//静态成员可以直接使用类访问,而不需要先实例化对象
echo Saler::$count;
- 静态方法:在勒种定义方法的时候使用static关键字修饰,访问的时候使用类+范围解析操作符+静态方法名字()访问
<?php
class Saler{
//方法
public static function showClass(){
echo __CLASS__;
}
}
//类直接访问
Saler::showClass();
- 在类的内部也可以访问静态成员,同样是使用类名+范围解析操作符+静态属性/静态方法()
<?php
class Saler{
//属性
private static $count = 0; //私有,不允许外部直接访问
//方法
public static function showClass(){
echo Saler::$count;
}
}
//类直接访问
Saler::showClass();
- 静态方法本质也是类中定义的方法,因此也可以使用对象进行访问,但是不建议
<?php
class Saler{
//属性
private static $count = 0; //私有,不允许外部直接访问
//方法
public static function showClass(){
echo Saler::$count;
}
}
//对象访问静态方法
$s = new Saler();
$s->showClass(); //输出0
- 同理,方法也是在类内部,在编译时就存在,因此可以通过类来进行访问,使用范围解析操作符,但是非常不建议(会报错:因为类只允许访问静态成员和类常量)
<?php
class Saler{
public function testStatic(){
echo __FUNCTION__;
}
}
//类访问普通成员方法
Saler::testStatic(); //输出testStatic,但是报错,当前访问的不是静态方法
- 静态方法本质是给类访问,所以不允许在静态方法内部使用$this对象
<?php
class Saler{
public static function testStaticThis(){
var_dump($this); //致命错误:$this放到了不该放的位置
}
}
总结
- 为了保障类能直接访问数据和操作数据,可以在属性和方法前增加static关键字变成静态属性和静态方法
- 类通过类名+范围解析操作符+静态成员的方式进行访问
- 静态成员也收访问修饰限定符的限定,访问权限与普通属性和方法的限制一样
- 对象可以无条件访问静态方法,而类只能访问不带$this的普通方法(不建议)
- 静态成员是给类访问的,非静态成员是给对象访问的
- 静态成员的访问效率比非静态成员高,因此有种说法是能用静态的时候就不用非静态
self关键字
定义:self关键字是一种在类的内部(方法里面)使用,代替类名的写法。能够保障用户方便修改类名字。
- self是用来代替类名的,与范围解析操作符
::
一起使用的
<?php
class Saler{
//属性
private static $count = 0; //私有,不允许外部直接访问
//方法
public static function showClass(){
echo Saler::$count;
echo self::$count; //代替类名
}
}
- self也可以在类的内部方便实例化对象:比如构造方法被私有化之后,就没有办法在类外部实例化对象,此时可以在类内部进行对象实例化
<?php
class Saler{
//属性
private static $count = 0; //私有,不允许外部直接访问
private function __construct(){} //私有,不允许外部实例化(因为对象不能外部调用)
//方法
public static function getInstance(){
return new Saler(); //使用类名实例化
return new self(); //使用self关键字实例化
}
}
$s = Saler::getInstance();
总结
- self是一种在类内部用来代替类名的关键字
- self可以用来在类内部访问静态成员(类常量也可以)
- self也可以在类内部用来实例化对象(代替类名:new self())
类的加载
定义:所谓类的加载,本质是因为类的访问必须保证类在内存中已经存在,所以需要在用类之前将类所在的PHP文件加载到内存。
- 手动加载:即要访问某个类之前,使用文件包含将类所在的文件加载进来
类文件:Saler.php
<?php
class Saler{}
?>
应用文件:useSaler.php
<?php
//使用Saler类需要先包含Saler类所在的文件
include_once 'Saler.php'; //通常使用include_once,因为类不允许重名
$s = new Saler();
?>
- 加载类文件是一种比较消耗资源的方式,所以有的时候不确定类是否在内存中存在,可以事先使用class_exists()函数来判定是否存在,存在就不用加载,不存在才加载
<?php
//使用Saler类,但是不确定内存中是否存在
if(!class_exists('Saler')){
//不存在:加载
include_once 'Saler.php';
}
//使用
$s = new Saler();
?>
- 自动加载:PHP没有那么智能的系统自动加载,所谓自动加载只是PHP提供了一种
加载机制
:即实现定义一个函数autoload(),然后当系统需要使用类,而内存中又不存在的时候,系统就会自动调用autoload()函数来加载类文件.
<?php
//自动加载机制:利用系统提供的__autoload()函数
function __autoload($classname){ //参数为类名:即当前需要访问的类的名字
//需要人为定义去哪加载,怎么加载
include_once $classname . '.php'; //假定为当前目录下,类文件名字为:类名.php
}
//使用类:内存目前并没有
$s = new Saler(); //系统发现内存没有Saler,所以调用__autoload()去加载
?>
- 一个系统里,可能类文件会放到不同的路径下,因此一个完整的自动加载函数,应该要进行文件判定以及加载功能
<?php
//定义自动加载
function __autoload($classname){
//组织文件路径:假设当前路径下,有两个文件夹下都有类c和m
$c_file = 'c/' . $classname . '.php'; //如c/Saler.php
if(file_exists($c_file)){
include_once $c_file;
}else{
//说明c文件夹没有对应的文件
$m_file = 'm/' . $classname . '.php'; //如m/Saler.php
if(file_exists($m_file)){
include_once $m_file;
}
}
}
?>
注意:自动加载是指按照开发者规定的路径去寻找对应的文件,并实现包含。如果文件不存在,那么系统会在使用类的时候报错,因为这是开发者自己犯的错,系统不能规避。
- 随着PHP版本的提升,在7以后,不怎么建议直接使用autoload()函数,而是采用一种注册机制,将用户自定义的函数,放到系统内部,使用spl_autoload_register(定义好的函数)。本质与autoload()一样
<?php
//定义一个函数,用来加载类文件
function my_autoload($classname){ //也需要一个参数来接收要加载的类名字
//功能与__autoload()一样
$c_file = 'c/' . $classname . '.php'; //如c/Saler.php
if(file_exists($c_file)){
include_once $c_file;
}else{
//说明c文件夹没有对应的文件
$m_file = 'm/' . $classname . '.php'; //如m/Saler.php
if(file_exists($m_file)){
include_once $m_file;
}
}
}
//此时,上述函数永远不会自动运行,除非将函数注册到系统内部
spl_autoload_register('my_autoload');
?>
注意:该方式其实本质就是通过两步完成了__autoload()一步的操作,但是spl_autoload_register()函数可以注册多个自定义的加载函数,更方便管理。
<?php
function c_autoload($classname){
$c_file = 'c/' . $classname . '.php';
if(file_exists($c_file)){
include_once $c_file;
}
}
function m_autoload($classname){
$m_file = 'm/' . $classname . '.php';
if(file_exists($m_file)){
include_once $m_file;
}
}
//全部注册
spl_autoload_register('c_autoload'); //先尝试调用第一个自定义加载函数:找到了结束;找不到找第二个函数
spl_autoload_register('m_autoload');
?>
总结
- 类的使用必须先保证内存中该类存在
- 可以使用手动加载来确保类的使用安全:优点是明确,缺点是繁琐(类文件名字可以随意没有规范)
- 可以使用自动加载来让系统按照开发者设定的路径和方式去寻找类,并尝试加载到内存(尽量让类文件名字统一,保证类名和文件名有关联)
- 自动加载可以使用__autoload()函数来实现,也可以使用自定义函数+spl_autoload_register()注册共同实现(后者推荐)
- 基本上所有的框架都在使用自动加载机制
对象克隆
定义:克隆对象clone,即通过已有的对象复制一个新的同样的对象,但是两者之间并非同一个对象。
- 对象克隆是通过clone关键字实现,即:clone 对象;
<?php
class Saler{
//属性
public $count;
private $money;
}
//实例化
$s1 = new Saler();
$s1->count = 1;
//克隆
$s2 = clone $s1;
?>
- 克隆出来的对象与原来对象是两个内存地址,因此是两个不同的对象
<?php
//接上述代码
$s2->count = 2;
echo $s1->count; //1,没有变化
?>
- 对象在实例化的时候会自动调用存在的构造方法construct(),同样的,在类的内部,PHP允许定义一个clone()的方法,在对象被克隆后,新克隆出来的对象会自动调用
<?php
class Saler{
//属性
public $count;
private $money;
//克隆方法
public function __clone(){
var_dump($this); //编号为2,代表是克隆出来的对象
$this->count++;
}
}
//实例化
$s1 = new Saler();
$s1->count = 1;
//克隆
$s2 = clone $s1;
?>
- 如果不允许对象被克隆,可以将__clone()方法私有化(本质是不允许对象在外部被克隆)
<?php
class Saler{
//属性
public $count;
private $money;
//私有化克隆方法
private function __clone(){}
}
//实例化
$s1 = new Saler();
$s1->count = 1;
//克隆
$s2 = clone $s1; //致命错误:不允许对象在外部访问一个私有方法
?>
总结
- 对象可以通过克隆来得到新的对象(以前只有实例化)
- 克隆出来的对象会自动调用类中对应的__clone()方法(如果有)
- 可以通过私有化克隆方法来实现禁止外部对象克隆
封装数据库操作类
定义:封装数据库操作类,即根据数据库的操作需求,来确认数据库操作类该有什么样的功能,以及这些功能该如何实现。
- 一个类通常就是一个文件,所以要先确定文件的名字:通常类文件命名规范有两种
- 文件名字与类名字一样,如Sql.php
- 为了区分普通PHP文件,增加中间类描述,如Sql.class.php
- 现在PHP几乎都是面向对象编程,所以通常采用第一种方式:因此当前命名数据类的文件为:Sql.php
- 确定类文件名字后其实也就确定了类名字,因此可以创建一个Sql类
<?php
//数据库操作类
class Sql{}
?>
- 类的创建分两种:一是特定使用,即类里面的所有内容只为某次使用;二是通用,即工具类,以后很多地方可以用。
- 特定使用,功能可以不用太灵活
- 通用工具,功能应该大众化,数据的变化会比较多
数据库类以后凡是要操作数据库的地方都可以用得到,很多项目都会用到,所以应该是个通用工具类,因此要考虑其到处可用的特性,让其能够灵活
- 数据库的操作最基本的特性不会改变:即需要连接认证,而连接认证的信息是灵活的,所以可以通过设定属性来控制,这些信息也都是不同使用者不同的,应该可以改变,所以可以通过构造方法来实现数据传入
<?php
//数据库操作类
class Sql{
//设置属性
public $host;
public $port;
public $user;
public $pass;
public $dbname;
public $charset;
//构造方法初始化数据:数据较多,应该使用数组来传递数据,关联数组,而且绝大部分的开发者本意是用来测试,所以基本都是本地,因此可以给默认数据
/*
$info = array(
'host' => 'localhost',
'port' => '3306',
'user' => 'root',
'pass' => 'root',
'dbname' => 'test',
'charset' => 'utf8'
)
*/
public function __construct(array $info = array()){
//初始化:确保用户传入了数据,否则使用默认值
$this->host = isset($info['host']) ? $info['host'] : 'localhost';
$this->port = isset($info['port']) ? $info['port'] : '3306';
$this->user = isset($info['user']) ? $info['user'] : 'root';
$this->pass = isset($info['pass']) ? $info['pass'] : 'root';
$this->dbname = isset($info['dbname']) ? $info['dbname'] : 'test';
$this->charset = isset($info['charset']) ? $info['charset'] : 'utf8';
}
}
?>
注意:方法设定的原则是一个方法只实现一个简单的功能,不要多个功能堆积到一个方法中。
- 数据库属性会在实例化Sql对象的时候自动初始化
<?php
//接上述代码(类外测试)
$s1 = new Sql(); //使用默认数据库信息
$db = array(
'host' => 'localhost',
'user' => 'root',
'pass' => 'root',
'dbname' => 'my_database'
);
$s2 = new Sql($db); //使用外部数据库信息
?>
- 数据库要操作的第一件事就是连接认证,所以需要一个连接认证的功能。这里可以使用mysqli面向对象的方法。但是需要建立一个方法来实现连接认证:连接是否成功?
//在上述类中增加一个方法:实现连接认证功能
public function sql_connect(){
//利用属性可以跨方法访问:5个参数分别为:主机、用户名、密码、数据库、端口
//利用错误抑制符抑制可能出现的错误:如找不到数据库之类
$link = @new mysqli($this->host,$this->user,$this->pass,$this->dbname,$this->port);
//判定连接是否成功
if($link->connect_error){
//mysqli对象有两个属性:connect_errno表示错误编号,connect_error表示错误信息:发生错误后终止脚本执行
die( 'Connect Error (' . $link -> connect_errno . ') ' . $link -> connect_error );
}
}
- 用户调用Sql类的目的一定是为了操作数据库,那么用户在实例化之后就需要调用连接认证的方法。为了方便用户操作,可以帮助用户省去调用这一步骤:在构造方法中调用该方法
//在上述构造方法中调用连接认证方法
public function __construct(array $info = array()){
//在属性初始化之后调用连接认证方法
$this->sql_connnect();
}
- 至此,一旦实例化Sql类对象,就可以实现数据库的连接,但是此时还存在一个细节问题:字符集,为了保证数据库连接的正常操作,需要新增一个方法设定字符集
//在Sql类中增加设定字符集的方法
public function sql_charset(){
$sql = "set names {$this->charset}"; //组织SQL指令
//此时需要调用mysqli连接数据库得到的对象:发现$link在sql_connect方法中是一个局部变量,不能跨方法
}
- 由于sql_connect方法中得到的连接对象mysqli的
link提升为整个类的内部可用:提升为属性
//在Sql类中增加属性,保存对象
public $link;
//修改sql_connect方法,使用属性保存连接对象
public function sql_connect(){
$this->link = @new mysqli($this->host,$this->user,$this->pass,$this->dbname,$this->port);
//判定连接是否成功
if($link->connect_error){
//注意:连接资源保存在$this->link中,所以访问mysqli对象成员时需要$this->link->属性/方法()
die('Connect Error (' . $this->link->connect_errno . ') ' . $this->link->connect_error);
}
}
- 继续完成设定字符集的功能:使用属性保存的mysqli连接对象
//在Sql类中完善设定字符集的方法
public function sql_charset(){
$sql = "set names {$this->charset}"; //组织SQL指令
//调用mysqli::query
$this->link->query($sql);
}
- 设置字符集的SQL指令虽然简单,但是因为有数据来源于外部,所以存在出错的风险,因此需要进行SQL错误判定
//在Sql类中完善设定字符集的方法
public function sql_charset(){
$sql = "set names {$this->charset}"; //组织SQL指令
$res = $this->link->query($sql);
if(!$res){
//失败发挥false
die('Charset Error(' . $this->link->errno . ') ' . $this->link->error);
}
}
- 同样的,字符集的设置也是为了操作数据库的前置条件,属于初始化的一部分。因此需要字符集设置的方法在构造方法中调用,方便用户操作
//在Sql类的构造方法中调用字符集设置方法
public function __construct(array $info = array()){
//在连接数据库方法调用之后调用设置字符集方法
$this->sql_charset();
}
- 至此:数据库的初始化操作已经完成,此时要考虑的事情是用户调用数据库类是为了干什么?为了执行SQL指令,也就是增删改查。在mysqli中所有的SQL执行都是通过mysqli::query()方法执行,但是我们可以根据需求封装两个函数:写方法和查方法(包含一条和多条查询)
//在Sql类中增加一个写方法:SQL指令来自于调用处提供
public function sql_exec($sql){
//当前不知道SQL指令是什么样的,只是负责执行,所以由外部提供
$res = $this->link->query($sql);
//判断SQL有没有语法错误
if(!$res){
die('Sql Error(' . $this->link->errno . ') ' . $this->link->error);
}
//返回执行结果
return $res; //本质就是一个true
}
//在Sql类中增加一个读方法:Sql指令来自于调用处提供,同时设定参数供用户选择一条或全部结果
public function sql_query($sql,$all = false){
//$all代表是否获取多条记录,默认false只获取一条记录
$res = $this->link->query($sql);
//判断SQL有没有语法错误
if(!$res){
die('Sql Error(' . $this->link->errno . ') ' . $this->link->error);
}
//解析其中的结果:根据用户需求获取一条或者多条记录
if($all){
//获取全部:mysqli_result::fetch_all(MYSQLI_ASSOC)表示返回关联数组(默认是索引)
return $res->fetch_all(MYSQLI_ASSOC);
}else{
//获取一条:mysqli_result::fetch_assoc()
return $res->fetch_assoc();
}
}
- 上述已经完成了数据库类要实现的基本功能:实现SQL指令的执行和结果返回,但是从功能细节的角度出发还需要进行完善:插入操作后要获取自增长id,更新和删除操作受影响的行数,查询操作中记录数量。这种使用可以通过设置方法来实现获取(自增长id),也可以通过增加属性来实现(属性简单)
增加属性:受影响的行数,自增长id,查询记录数
//在Sql类中追加属性
public $affected_rows; //受影响行数(上次操作)
public $num_rows; //查询结果记录数(上次操作)
在写操作sql_exec中,为受影响行数赋值
//修改sql_exec方法
public function sql_exec($sql){
$res = $this->link->query($sql);
//判断SQL有没有语法错误
if(!$res){
die('Sql Error(' . $this->link->errno . ') ' . $this->link->error);
}
//成功保存受影响的行数:数据在mysqli对象中
$this->affected_rows = $this->link->affected_rows;
//返回执行结果
return $res; //本质就是一个true
}
在读操作中,为记录数
//修改sql_query方法
public function sql_query($sql,$all = false){
$res = $this->link->query($sql);
//判断SQL有没有语法错误
if(!$res){
die('Sql Error(' . $this->link->errno . ') ' . $this->link->error);
}
//获取记录信息:数据在mysqli_result对象中
$this->num_rows = $res->num_rows;
//解析其中的结果:根据用户需求获取一条或者多条记录
if($all){
//获取全部:mysqli_result::fetch_all(MYSQLI_ASSOC)表示返回关联数组(默认是索引)
return $res->fetch_all(MYSQLI_ASSOC);
}else{
//获取一条:mysqli_result::fetch_assoc()
return $res->fetch_assoc();
}
}
增加一个方法专门获取上次插入数据的自增长ID(因为这个是人为区分,不方便放到sql_exec中)
//在Sql类中增加一个方法获取上一次自增长操作id
public function sql_insert_id(){
//insertid是在mysqli对象执行query方法时获得
return $this->link->insert_id;
}
- 至此:数据库类的功能已经实现,接下来要考虑类的定义规范:类对成员的控制性
- 属性如果不需要给外部访问,私有
- 方法如果只是内部调用,私有
- 测试:利用数据库类实现数据库的写操作和读操作
总结
- 类的封装是以功能驱动为前提,相关操作存放到一个类中
- 一个类通常是一个独立的文件,文件名与类名相同(方便后期维护和自动加载)
- 类中如果有数据需要管理,设定属性(固定数据可以使用类常量)
- 类中如果有功能需要实现(数据加工),设定方法
- 一个功能通常使用一个方法实现,方法的颗粒度应该尽可能小(方便复用)
- 应该尽可能增加类对成员的控制:即能私有尽可能私有