面向对象开发的规范

定义:面向对象规范是指我们在开发时,在技术没有强制规定的情况下,应该采用什么样的方式来协助我们进行代码的书写。

  1. 属性的初始化:属性是类对于同类事务所抽离出来的共性数据,本身在类结构中没有价值,是当具体对象产生之后,属于对象本身的。因此在进行类中定义属性的时候,通常不会对属性进行初始化,除非属性本身的值也具有共性
  1. <?php
  2. class Saler{
  3. //属性
  4. public $count;
  5. //某个卖家拥有的商品数量,每位具体卖家对象拥有的不可能一样,所以没必要初始化
  6. protected $discount;
  7. //某个卖家针对销售的折扣,同样没有统一的价值
  8. private $money = 0;
  9. //某个卖家的账户余额,任何一位卖家一开始做生意的时候,账户余额都为0,所以可以初始化
  10. ?>
  1. 属性的初始化:属性在类中初始化的基本判定就是数据是否初始化的时候统一,如果数据统一(即初始化),那么所有对象在生成之后,就拥有对应的值;如果不初始化,意味着所有对象在生成后,应该对属性进行赋值。
  1. <?php
  2. class Saler{
  3. //属性
  4. public $count;
  5. private $money = 0;
  6. }
  7. //实例化
  8. $s1 = new Saler();
  9. $s2 = new Saler(); //所有对象都有两个属性,其中$count都没有数据,而$money都为0
  10. //生成对象后,对象对自己的属性进行初始化
  11. $s1->count = 100;
  12. $s2->count = 1000;
  13. ?>
  1. 访问修饰限定符选择:访问修饰限定符是用来限制类成员被对象访问时对象所处位置的。访问的权限从public、protected到private依次变小。使用规则如下:
  • 设定好的类成员本身不会被外部用到,那么应该使用private或者protected
  • 设定好的类成员一定会给外部访问,使用public
  • 属性通常private居多,如果需要外部访问属性,通常会定义相关方法来实现属性的查看和修改,因为可以在方法内对数据逻辑进行代码控制,安全
  • 总之一句话:尽可能增加类对成员的控制(尽可能多使用private,少使用public)
  1. <?php
  2. class Saler{
  3. //属性
  4. public $count;
  5. private $money = 0;
  6. //增加方法操作私有属性money
  7. public function getMoney(){
  8. return $this->money;
  9. }
  10. public function setMoney($money){
  11. //可以对逻辑进行修改,对数据进行安全判定,保证数据的安全性
  12. $this->money = $money;
  13. //$this->money是属性,$money是外部传入的参数,二者同名但是性质完全不同
  14. }
  15. }

总结

  1. 属性在类中定义的时候,通常不会初始化值,除非所有类实例化得到的对象的某个属性需要是统一值
  2. 应该尽可能增加类对成员的控制,即使用范围较小的访问修饰限定符优先
  3. 属性通常是私有化的,一般是通过设定方法来实现属性的访问和修改

构造方法

定义:构造方法__construct(),是一种类结构特有的特殊方法,该方法由系统规定好,开发人员在定义的时候只需要写抄一遍,有了构造方法的类在实例化对象之后,对象就会自动调用。
  1. 构造方法实现:在类中增加一个方法__construct()即可
  1. <?php
  2. class Saler{
  3. //属性
  4. public $count;
  5. private $money;
  6. //构造方法
  7. public function __construct(){
  8. echo __CLASS__;
  9. }
  10. }
  1. 构造方法也是一个普通方法,不普通的地方在于,类实例化得到的对象会马上自动调用
  1. <?php
  2. //接上述代码
  3. new Saler(); //输出Saler
  1. 构造方法的意义:构造方法是对象实例化的时候用来初始化对象的资源的,所以通常是用来初始化对象的属性或者其他资源初始化
  1. <?php
  2. class Saler{
  3. //属性
  4. public $count;
  5. private $money;
  6. //构造方法:初始化属性
  7. public function __construct(){
  8. $this->count = 100;
  9. $this->money = 100;
  10. }
  11. }
  1. 如果属性的数据在构造方法中初始化是固定写死的,那么与直接在定义类的时候初始化属性一样。意味着数据没有任何价值(所有对象都相同),因此通常是通过构造方法的参数来实现数据的外部传入
  1. <?php
  2. class Saler{
  3. //属性
  4. public $count;
  5. private $money;
  6. //构造方法:初始化属性
  7. public function __construct($count,$money){
  8. $this->count = $count;
  9. $this->money = $money;
  10. }
  11. }
  1. 一旦构造方法拥有了形参,那么对象在调用该方法的时候就需要传入对应的实参,而构造方法又是自动调用的,所以需要在实例化对象的时候使用new 类名(构造方法对应的实参列表)来实现
  1. <?php
  2. //接上述代码
  3. $s1 = new Saler(100,100);
  4. $s2 = new Saler(1000,1000);

注意:之前所说的new 类名new 类名()没有区别是因为没有构造方法,或者构造方法没有参数限定,一旦构造方法有了参数,那么new 类名 就不能直接使用了。

  1. 构造方法不管再怎么特殊,也是用户定义的方法,言外之意除了在实例化对象时对象会自动调用之外,我们也可以手动调用构造方法(但是一般没有价值,因为对象实例化时会自动调用)
  1. <?php
  2. class Saler{
  3. //属性
  4. public $count;
  5. private $money;
  6. //构造方法:初始化属性
  7. public function __construct($count,$money){
  8. $this->count = $count;
  9. $this->money = $money;
  10. }
  11. }
  12. //实例化
  13. $s = new Saler(100,100); //系统在new Saler(100,100)好之后,会自动调用一次
  14. $s->__construct(1000,1000); //允许手动调用

总结

  1. 构造方法__construct()是一种系统内置的方法,该方法的特性是会在对象实例化之后,对象立即自动调用
  2. 构造方法的目的就是为了初始化资源,包含对象属性和其他资源
  3. 一旦构造方法定义好之后,且构造方法自带参数,那么就只能使用new 类名(参数列表)方式才能正确实例化
  4. 构造方法可以当做普通方法由对象调用(不建议)

析构方法

定义:析构方法__destruct(),也是一种类结构中特殊的方法,与构造方法一样,也是系统规定好,只需要开发人员一遍即可,对象在被销毁时会自动调用。

  1. 析构方法实现:类中增加一个__destruct()方法
  1. <?php
  2. class Saler{
  3. //析构方法
  4. public function __destruct(){
  5. echo __FUNCTION__;
  6. }
  7. }
  1. 析构方法调用:析构方法是在对象被销毁时自动,对象的“垂死挣扎”
  1. <?php
  2. //接上述代码
  3. //实例化
  4. $s = new Saler();
  5. unset($s); //删除对象
  1. 析构方法是对象用来调用释放对象中的资源,不是用来删除对象的
  1. <?php
  2. class Saler{
  3. //析构方法
  4. public function __destruct(){
  5. //销毁对象所占用资源的代码
  6. }
  7. }
  1. 析构方法也是普通方法,可以由对象直接调用
  1. <?php
  2. //接析构方法实现代码
  3. $s = new Saler();
  4. $s->__destruct(); //思考:此时对象是否被销毁?
  1. 对象销毁情形
  • 保存对象的变量被用来保存其他数据,导致对象内存没有任何变量引用
  • 删除对象变量
  • 脚本执行结束:释放所有变量

总结

  1. 析构方法是一种对象销毁时自动调用的方法
  2. 析构方法是用来对象销毁自身所占用的资源
  3. PHP中脚本执行结束,系统会自动回收所有资源,因此一般PHP中很少使用析构方法

对象传值

定义:对象传值,其实就是将保存对象的变量赋值给另外一个变量,在PHP中,对象的传值是引用传递的:即一个对象变量赋值给另外一个变量,两个变量指向同一个对象的内存地址,即只有一个对象。

  1. 对象传值就是保存对象的变量赋值给另外一个变量
  1. <?php
  2. class Saler{}
  3. $s1 = new Saler();
  4. $s2 = $s1;
  1. 对象传值是引用传递,不管对象赋值给多少个变量,内存中只有一个对象
  1. <?php
  2. class Saler{}
  3. $s1 = new Saler();
  4. $s2 = $s1;
  5. //证明
  6. var_dump($s1,$s2); //同一个对象
  7. $s1->name = 'Saler'; //更改一个变量所保存对象的属性
  8. echo $s2->name; //输出Saler

总结:对象传值是引用传值,一般情况下不会在项目中用到。


范围解析操作符

定义:范围解析操作符,由两个冒号组成“::”,是专门用于类实现类成员操作的,可以实现类直接访问类成员。

  1. 类常量的普通访问尝试:尝试使用对象进行访问
  1. <?php
  2. class Saler{
  3. //类常量
  4. const PI = 3.14;
  5. }
  6. $s1 = new Saler();
  7. echo $s1->PI; //错误,$s1->PI最终转换的访问方式为:$PI,这个在类中并不存在
  1. 以上案例可以看出,对象无法访问类常量,那是因为类常量的定义本身就是用来给类访问的,对象是用来访问属性和方法的,类常量的访问方式为:类名::常量名
  1. <?php
  2. class Saler{
  3. //类常量
  4. const PI = 3.14;
  5. }
  6. echo Saler::PI; //输出3.14
  1. 分析:类常量是固定的,而对象的属性是不同对象而不同的,成员方法简单的理解也是为属性本身进行加工的。因此有一些东西是专属于类的,而有部分内容是专门为对象提供的,所以就会有不同的成员拥有不同的访问方式

总结

  1. 类访问成员的方式是使用范围解析操作符“::”访问,由类名直接访问:类名::类常量
  2. 类本身是通过对同类对象的抽象而形成,所以属性和方法本身都是由对象来访问
  3. 类也需要有一些自身的数据和操作,这些就由类来进行访问

静态成员

定义:静态成员,使用static关键字修饰的类成员,表示该成员属于类访问。PHP静态成员有两种,静态属性和静态方法。

  1. 静态属性:在类中定义属性的时候使用static关键字修饰,访问的时候只能使用类+范围解析操作符+静态属性访问
  1. <?php
  2. class Saler{
  3. //属性
  4. public $money = 0;
  5. public static $count = 0; //静态属性
  6. }
  7. //静态成员可以直接使用类访问,而不需要先实例化对象
  8. echo Saler::$count;
  1. 静态方法:在勒种定义方法的时候使用static关键字修饰,访问的时候使用类+范围解析操作符+静态方法名字()访问
  1. <?php
  2. class Saler{
  3. //方法
  4. public static function showClass(){
  5. echo __CLASS__;
  6. }
  7. }
  8. //类直接访问
  9. Saler::showClass();
  1. 在类的内部也可以访问静态成员,同样是使用类名+范围解析操作符+静态属性/静态方法()
  1. <?php
  2. class Saler{
  3. //属性
  4. private static $count = 0; //私有,不允许外部直接访问
  5. //方法
  6. public static function showClass(){
  7. echo Saler::$count;
  8. }
  9. }
  10. //类直接访问
  11. Saler::showClass();
  1. 静态方法本质也是类中定义的方法,因此也可以使用对象进行访问,但是不建议
  1. <?php
  2. class Saler{
  3. //属性
  4. private static $count = 0; //私有,不允许外部直接访问
  5. //方法
  6. public static function showClass(){
  7. echo Saler::$count;
  8. }
  9. }
  10. //对象访问静态方法
  11. $s = new Saler();
  12. $s->showClass(); //输出0
  1. 同理,方法也是在类内部,在编译时就存在,因此可以通过类来进行访问,使用范围解析操作符,但是非常不建议(会报错:因为类只允许访问静态成员和类常量)
  1. <?php
  2. class Saler{
  3. public function testStatic(){
  4. echo __FUNCTION__;
  5. }
  6. }
  7. //类访问普通成员方法
  8. Saler::testStatic(); //输出testStatic,但是报错,当前访问的不是静态方法
  1. 静态方法本质是给类访问,所以不允许在静态方法内部使用$this对象
  1. <?php
  2. class Saler{
  3. public static function testStaticThis(){
  4. var_dump($this); //致命错误:$this放到了不该放的位置
  5. }
  6. }

总结

  1. 为了保障类能直接访问数据和操作数据,可以在属性和方法前增加static关键字变成静态属性和静态方法
  2. 类通过类名+范围解析操作符+静态成员的方式进行访问
  3. 静态成员也收访问修饰限定符的限定,访问权限与普通属性和方法的限制一样
  4. 对象可以无条件访问静态方法,而类只能访问不带$this的普通方法(不建议)
  5. 静态成员是给类访问的,非静态成员是给对象访问的
  6. 静态成员的访问效率比非静态成员高,因此有种说法是能用静态的时候就不用非静态

self关键字

定义:self关键字是一种在类的内部(方法里面)使用,代替类名的写法。能够保障用户方便修改类名字。

  1. self是用来代替类名的,与范围解析操作符::一起使用的
  1. <?php
  2. class Saler{
  3. //属性
  4. private static $count = 0; //私有,不允许外部直接访问
  5. //方法
  6. public static function showClass(){
  7. echo Saler::$count;
  8. echo self::$count; //代替类名
  9. }
  10. }
  1. self也可以在类的内部方便实例化对象:比如构造方法被私有化之后,就没有办法在类外部实例化对象,此时可以在类内部进行对象实例化
  1. <?php
  2. class Saler{
  3. //属性
  4. private static $count = 0; //私有,不允许外部直接访问
  5. private function __construct(){} //私有,不允许外部实例化(因为对象不能外部调用)
  6. //方法
  7. public static function getInstance(){
  8. return new Saler(); //使用类名实例化
  9. return new self(); //使用self关键字实例化
  10. }
  11. }
  12. $s = Saler::getInstance();

总结

  1. self是一种在类内部用来代替类名的关键字
  2. self可以用来在类内部访问静态成员(类常量也可以)
  3. self也可以在类内部用来实例化对象(代替类名:new self())

类的加载

定义:所谓类的加载,本质是因为类的访问必须保证类在内存中已经存在,所以需要在用类之前将类所在的PHP文件加载到内存。

  1. 手动加载:即要访问某个类之前,使用文件包含将类所在的文件加载进来
  1. 类文件:Saler.php
  2. <?php
  3. class Saler{}
  4. ?>
  5. 应用文件:useSaler.php
  6. <?php
  7. //使用Saler类需要先包含Saler类所在的文件
  8. include_once 'Saler.php'; //通常使用include_once,因为类不允许重名
  9. $s = new Saler();
  10. ?>
  1. 加载类文件是一种比较消耗资源的方式,所以有的时候不确定类是否在内存中存在,可以事先使用class_exists()函数来判定是否存在,存在就不用加载,不存在才加载
  1. <?php
  2. //使用Saler类,但是不确定内存中是否存在
  3. if(!class_exists('Saler')){
  4. //不存在:加载
  5. include_once 'Saler.php';
  6. }
  7. //使用
  8. $s = new Saler();
  9. ?>
  1. 自动加载:PHP没有那么智能的系统自动加载,所谓自动加载只是PHP提供了一种加载机制:即实现定义一个函数autoload(),然后当系统需要使用类,而内存中又不存在的时候,系统就会自动调用autoload()函数来加载类文件.
  1. <?php
  2. //自动加载机制:利用系统提供的__autoload()函数
  3. function __autoload($classname){ //参数为类名:即当前需要访问的类的名字
  4. //需要人为定义去哪加载,怎么加载
  5. include_once $classname . '.php'; //假定为当前目录下,类文件名字为:类名.php
  6. }
  7. //使用类:内存目前并没有
  8. $s = new Saler(); //系统发现内存没有Saler,所以调用__autoload()去加载
  9. ?>
  1. 一个系统里,可能类文件会放到不同的路径下,因此一个完整的自动加载函数,应该要进行文件判定以及加载功能
  1. <?php
  2. //定义自动加载
  3. function __autoload($classname){
  4. //组织文件路径:假设当前路径下,有两个文件夹下都有类c和m
  5. $c_file = 'c/' . $classname . '.php'; //如c/Saler.php
  6. if(file_exists($c_file)){
  7. include_once $c_file;
  8. }else{
  9. //说明c文件夹没有对应的文件
  10. $m_file = 'm/' . $classname . '.php'; //如m/Saler.php
  11. if(file_exists($m_file)){
  12. include_once $m_file;
  13. }
  14. }
  15. }
  16. ?>

注意:自动加载是指按照开发者规定的路径去寻找对应的文件,并实现包含。如果文件不存在,那么系统会在使用类的时候报错,因为这是开发者自己犯的错,系统不能规避。

  1. 随着PHP版本的提升,在7以后,不怎么建议直接使用autoload()函数,而是采用一种注册机制,将用户自定义的函数,放到系统内部,使用spl_autoload_register(定义好的函数)。本质与autoload()一样
  1. <?php
  2. //定义一个函数,用来加载类文件
  3. function my_autoload($classname){ //也需要一个参数来接收要加载的类名字
  4. //功能与__autoload()一样
  5. $c_file = 'c/' . $classname . '.php'; //如c/Saler.php
  6. if(file_exists($c_file)){
  7. include_once $c_file;
  8. }else{
  9. //说明c文件夹没有对应的文件
  10. $m_file = 'm/' . $classname . '.php'; //如m/Saler.php
  11. if(file_exists($m_file)){
  12. include_once $m_file;
  13. }
  14. }
  15. }
  16. //此时,上述函数永远不会自动运行,除非将函数注册到系统内部
  17. spl_autoload_register('my_autoload');
  18. ?>

注意:该方式其实本质就是通过两步完成了__autoload()一步的操作,但是spl_autoload_register()函数可以注册多个自定义的加载函数,更方便管理。

  1. <?php
  2. function c_autoload($classname){
  3. $c_file = 'c/' . $classname . '.php';
  4. if(file_exists($c_file)){
  5. include_once $c_file;
  6. }
  7. }
  8. function m_autoload($classname){
  9. $m_file = 'm/' . $classname . '.php';
  10. if(file_exists($m_file)){
  11. include_once $m_file;
  12. }
  13. }
  14. //全部注册
  15. spl_autoload_register('c_autoload'); //先尝试调用第一个自定义加载函数:找到了结束;找不到找第二个函数
  16. spl_autoload_register('m_autoload');
  17. ?>

总结

  1. 类的使用必须先保证内存中该类存在
  2. 可以使用手动加载来确保类的使用安全:优点是明确,缺点是繁琐(类文件名字可以随意没有规范)
  3. 可以使用自动加载来让系统按照开发者设定的路径和方式去寻找类,并尝试加载到内存(尽量让类文件名字统一,保证类名和文件名有关联)
  4. 自动加载可以使用__autoload()函数来实现,也可以使用自定义函数+spl_autoload_register()注册共同实现(后者推荐)
  5. 基本上所有的框架都在使用自动加载机制

对象克隆

定义:克隆对象clone,即通过已有的对象复制一个新的同样的对象,但是两者之间并非同一个对象。

  1. 对象克隆是通过clone关键字实现,即:clone 对象;
  1. <?php
  2. class Saler{
  3. //属性
  4. public $count;
  5. private $money;
  6. }
  7. //实例化
  8. $s1 = new Saler();
  9. $s1->count = 1;
  10. //克隆
  11. $s2 = clone $s1;
  12. ?>
  1. 克隆出来的对象与原来对象是两个内存地址,因此是两个不同的对象
  1. <?php
  2. //接上述代码
  3. $s2->count = 2;
  4. echo $s1->count; //1,没有变化
  5. ?>
  1. 对象在实例化的时候会自动调用存在的构造方法construct(),同样的,在类的内部,PHP允许定义一个clone()的方法,在对象被克隆后,新克隆出来的对象会自动调用
  1. <?php
  2. class Saler{
  3. //属性
  4. public $count;
  5. private $money;
  6. //克隆方法
  7. public function __clone(){
  8. var_dump($this); //编号为2,代表是克隆出来的对象
  9. $this->count++;
  10. }
  11. }
  12. //实例化
  13. $s1 = new Saler();
  14. $s1->count = 1;
  15. //克隆
  16. $s2 = clone $s1;
  17. ?>
  1. 如果不允许对象被克隆,可以将__clone()方法私有化(本质是不允许对象在外部被克隆)
  1. <?php
  2. class Saler{
  3. //属性
  4. public $count;
  5. private $money;
  6. //私有化克隆方法
  7. private function __clone(){}
  8. }
  9. //实例化
  10. $s1 = new Saler();
  11. $s1->count = 1;
  12. //克隆
  13. $s2 = clone $s1; //致命错误:不允许对象在外部访问一个私有方法
  14. ?>

总结

  1. 对象可以通过克隆来得到新的对象(以前只有实例化)
  2. 克隆出来的对象会自动调用类中对应的__clone()方法(如果有)
  3. 可以通过私有化克隆方法来实现禁止外部对象克隆

封装数据库操作类

定义:封装数据库操作类,即根据数据库的操作需求,来确认数据库操作类该有什么样的功能,以及这些功能该如何实现。

  1. 一个类通常就是一个文件,所以要先确定文件的名字:通常类文件命名规范有两种
  • 文件名字与类名字一样,如Sql.php
  • 为了区分普通PHP文件,增加中间类描述,如Sql.class.php
  • 现在PHP几乎都是面向对象编程,所以通常采用第一种方式:因此当前命名数据类的文件为:Sql.php
  1. 确定类文件名字后其实也就确定了类名字,因此可以创建一个Sql类
  1. <?php
  2. //数据库操作类
  3. class Sql{}
  4. ?>
  1. 类的创建分两种:一是特定使用,即类里面的所有内容只为某次使用;二是通用,即工具类,以后很多地方可以用。
  • 特定使用,功能可以不用太灵活
  • 通用工具,功能应该大众化,数据的变化会比较多

数据库类以后凡是要操作数据库的地方都可以用得到,很多项目都会用到,所以应该是个通用工具类,因此要考虑其到处可用的特性,让其能够灵活

  1. 数据库的操作最基本的特性不会改变:即需要连接认证,而连接认证的信息是灵活的,所以可以通过设定属性来控制,这些信息也都是不同使用者不同的,应该可以改变,所以可以通过构造方法来实现数据传入
  1. <?php
  2. //数据库操作类
  3. class Sql{
  4. //设置属性
  5. public $host;
  6. public $port;
  7. public $user;
  8. public $pass;
  9. public $dbname;
  10. public $charset;
  11. //构造方法初始化数据:数据较多,应该使用数组来传递数据,关联数组,而且绝大部分的开发者本意是用来测试,所以基本都是本地,因此可以给默认数据
  12. /*
  13. $info = array(
  14. 'host' => 'localhost',
  15. 'port' => '3306',
  16. 'user' => 'root',
  17. 'pass' => 'root',
  18. 'dbname' => 'test',
  19. 'charset' => 'utf8'
  20. )
  21. */
  22. public function __construct(array $info = array()){
  23. //初始化:确保用户传入了数据,否则使用默认值
  24. $this->host = isset($info['host']) ? $info['host'] : 'localhost';
  25. $this->port = isset($info['port']) ? $info['port'] : '3306';
  26. $this->user = isset($info['user']) ? $info['user'] : 'root';
  27. $this->pass = isset($info['pass']) ? $info['pass'] : 'root';
  28. $this->dbname = isset($info['dbname']) ? $info['dbname'] : 'test';
  29. $this->charset = isset($info['charset']) ? $info['charset'] : 'utf8';
  30. }
  31. }
  32. ?>

注意:方法设定的原则是一个方法只实现一个简单的功能,不要多个功能堆积到一个方法中。

  1. 数据库属性会在实例化Sql对象的时候自动初始化
  1. <?php
  2. //接上述代码(类外测试)
  3. $s1 = new Sql(); //使用默认数据库信息
  4. $db = array(
  5. 'host' => 'localhost',
  6. 'user' => 'root',
  7. 'pass' => 'root',
  8. 'dbname' => 'my_database'
  9. );
  10. $s2 = new Sql($db); //使用外部数据库信息
  11. ?>
  1. 数据库要操作的第一件事就是连接认证,所以需要一个连接认证的功能。这里可以使用mysqli面向对象的方法。但是需要建立一个方法来实现连接认证:连接是否成功?
  1. //在上述类中增加一个方法:实现连接认证功能
  2. public function sql_connect(){
  3. //利用属性可以跨方法访问:5个参数分别为:主机、用户名、密码、数据库、端口
  4. //利用错误抑制符抑制可能出现的错误:如找不到数据库之类
  5. $link = @new mysqli($this->host,$this->user,$this->pass,$this->dbname,$this->port);
  6. //判定连接是否成功
  7. if($link->connect_error){
  8. //mysqli对象有两个属性:connect_errno表示错误编号,connect_error表示错误信息:发生错误后终止脚本执行
  9. die( 'Connect Error (' . $link -> connect_errno . ') ' . $link -> connect_error );
  10. }
  11. }
  1. 用户调用Sql类的目的一定是为了操作数据库,那么用户在实例化之后就需要调用连接认证的方法。为了方便用户操作,可以帮助用户省去调用这一步骤:在构造方法中调用该方法
  1. //在上述构造方法中调用连接认证方法
  2. public function __construct(array $info = array()){
  3. //在属性初始化之后调用连接认证方法
  4. $this->sql_connnect();
  5. }
  1. 至此,一旦实例化Sql类对象,就可以实现数据库的连接,但是此时还存在一个细节问题:字符集,为了保证数据库连接的正常操作,需要新增一个方法设定字符集
  1. //在Sql类中增加设定字符集的方法
  2. public function sql_charset(){
  3. $sql = "set names {$this->charset}"; //组织SQL指令
  4. //此时需要调用mysqli连接数据库得到的对象:发现$link在sql_connect方法中是一个局部变量,不能跨方法
  5. }
  1. 由于sql_connect方法中得到的连接对象mysqli的5月8日学习 - 图1link提升为整个类的内部可用:提升为属性
  1. //在Sql类中增加属性,保存对象
  2. public $link;
  3. //修改sql_connect方法,使用属性保存连接对象
  4. public function sql_connect(){
  5. $this->link = @new mysqli($this->host,$this->user,$this->pass,$this->dbname,$this->port);
  6. //判定连接是否成功
  7. if($link->connect_error){
  8. //注意:连接资源保存在$this->link中,所以访问mysqli对象成员时需要$this->link->属性/方法()
  9. die('Connect Error (' . $this->link->connect_errno . ') ' . $this->link->connect_error);
  10. }
  11. }
  1. 继续完成设定字符集的功能:使用属性保存的mysqli连接对象
  1. //在Sql类中完善设定字符集的方法
  2. public function sql_charset(){
  3. $sql = "set names {$this->charset}"; //组织SQL指令
  4. //调用mysqli::query
  5. $this->link->query($sql);
  6. }
  1. 设置字符集的SQL指令虽然简单,但是因为有数据来源于外部,所以存在出错的风险,因此需要进行SQL错误判定
  1. //在Sql类中完善设定字符集的方法
  2. public function sql_charset(){
  3. $sql = "set names {$this->charset}"; //组织SQL指令
  4. $res = $this->link->query($sql);
  5. if(!$res){
  6. //失败发挥false
  7. die('Charset Error(' . $this->link->errno . ') ' . $this->link->error);
  8. }
  9. }
  1. 同样的,字符集的设置也是为了操作数据库的前置条件,属于初始化的一部分。因此需要字符集设置的方法在构造方法中调用,方便用户操作
  1. //在Sql类的构造方法中调用字符集设置方法
  2. public function __construct(array $info = array()){
  3. //在连接数据库方法调用之后调用设置字符集方法
  4. $this->sql_charset();
  5. }
  1. 至此:数据库的初始化操作已经完成,此时要考虑的事情是用户调用数据库类是为了干什么?为了执行SQL指令,也就是增删改查。在mysqli中所有的SQL执行都是通过mysqli::query()方法执行,但是我们可以根据需求封装两个函数:写方法和查方法(包含一条和多条查询)
  1. //在Sql类中增加一个写方法:SQL指令来自于调用处提供
  2. public function sql_exec($sql){
  3. //当前不知道SQL指令是什么样的,只是负责执行,所以由外部提供
  4. $res = $this->link->query($sql);
  5. //判断SQL有没有语法错误
  6. if(!$res){
  7. die('Sql Error(' . $this->link->errno . ') ' . $this->link->error);
  8. }
  9. //返回执行结果
  10. return $res; //本质就是一个true
  11. }
  12. //在Sql类中增加一个读方法:Sql指令来自于调用处提供,同时设定参数供用户选择一条或全部结果
  13. public function sql_query($sql,$all = false){
  14. //$all代表是否获取多条记录,默认false只获取一条记录
  15. $res = $this->link->query($sql);
  16. //判断SQL有没有语法错误
  17. if(!$res){
  18. die('Sql Error(' . $this->link->errno . ') ' . $this->link->error);
  19. }
  20. //解析其中的结果:根据用户需求获取一条或者多条记录
  21. if($all){
  22. //获取全部:mysqli_result::fetch_all(MYSQLI_ASSOC)表示返回关联数组(默认是索引)
  23. return $res->fetch_all(MYSQLI_ASSOC);
  24. }else{
  25. //获取一条:mysqli_result::fetch_assoc()
  26. return $res->fetch_assoc();
  27. }
  28. }
  1. 上述已经完成了数据库类要实现的基本功能:实现SQL指令的执行和结果返回,但是从功能细节的角度出发还需要进行完善:插入操作后要获取自增长id,更新和删除操作受影响的行数,查询操作中记录数量。这种使用可以通过设置方法来实现获取(自增长id),也可以通过增加属性来实现(属性简单)

增加属性:受影响的行数,自增长id,查询记录数

  1. //在Sql类中追加属性
  2. public $affected_rows; //受影响行数(上次操作)
  3. public $num_rows; //查询结果记录数(上次操作)

在写操作sql_exec中,为受影响行数赋值

  1. //修改sql_exec方法
  2. public function sql_exec($sql){
  3. $res = $this->link->query($sql);
  4. //判断SQL有没有语法错误
  5. if(!$res){
  6. die('Sql Error(' . $this->link->errno . ') ' . $this->link->error);
  7. }
  8. //成功保存受影响的行数:数据在mysqli对象中
  9. $this->affected_rows = $this->link->affected_rows;
  10. //返回执行结果
  11. return $res; //本质就是一个true
  12. }

在读操作中,为记录数

  1. //修改sql_query方法
  2. public function sql_query($sql,$all = false){
  3. $res = $this->link->query($sql);
  4. //判断SQL有没有语法错误
  5. if(!$res){
  6. die('Sql Error(' . $this->link->errno . ') ' . $this->link->error);
  7. }
  8. //获取记录信息:数据在mysqli_result对象中
  9. $this->num_rows = $res->num_rows;
  10. //解析其中的结果:根据用户需求获取一条或者多条记录
  11. if($all){
  12. //获取全部:mysqli_result::fetch_all(MYSQLI_ASSOC)表示返回关联数组(默认是索引)
  13. return $res->fetch_all(MYSQLI_ASSOC);
  14. }else{
  15. //获取一条:mysqli_result::fetch_assoc()
  16. return $res->fetch_assoc();
  17. }
  18. }

增加一个方法专门获取上次插入数据的自增长ID(因为这个是人为区分,不方便放到sql_exec中)

  1. //在Sql类中增加一个方法获取上一次自增长操作id
  2. public function sql_insert_id(){
  3. //insertid是在mysqli对象执行query方法时获得
  4. return $this->link->insert_id;
  5. }
  1. 至此:数据库类的功能已经实现,接下来要考虑类的定义规范:类对成员的控制性
  • 属性如果不需要给外部访问,私有
  • 方法如果只是内部调用,私有
  1. 测试:利用数据库类实现数据库的写操作和读操作

总结

  1. 类的封装是以功能驱动为前提,相关操作存放到一个类中
  2. 一个类通常是一个独立的文件,文件名与类名相同(方便后期维护和自动加载)
  3. 类中如果有数据需要管理,设定属性(固定数据可以使用类常量)
  4. 类中如果有功能需要实现(数据加工),设定方法
  5. 一个功能通常使用一个方法实现,方法的颗粒度应该尽可能小(方便复用)
  6. 应该尽可能增加类对成员的控制:即能私有尽可能私有