01 PHP面向对象基础

转载于:黑马程序员武汉中心

面向对象基础

学习目标:理解面向对象编程思想,了解计算机编程语言的演变过程,掌握PHP面向对象的基础语法,使用面向对象编程思想和面向对象语法实现编程解决需求问题

  • 计算机语言发展史
  • 面向过程编程思想
  • 面向对象编程思想
  • 面向对象基础语法
  • 综合运用

概念

编程语法发展史:计算机编程在历史的发展长河中,经历了多次版本变革,变化的轨迹是伴随着硬件的发展和人们对于计算机的认知以及需求。

  • 机器语言:即开发者(科学家)使用01组成命令,然后在特定计算机上执行

    • 优点:执行效率高

    • 缺点:开发难度大、移植性差、开发成本高

  • 汇编语言:开发者使用简洁英文字母符号组成,让计算机读取后根据符号进行加工执行

    • 优点:指令简单明了、推广性高
    • 缺点:移植性差、功能简单
  • 高级计算机语言:开发者使用类似自然语言的符号组成,高级语言根据编程思想分为面向过程编程面向对象编程两种,然后系统对程序代码进行编译(需要第三方编译器)然后执行

    • 优点:移植性强、可读性强、推广性非常高
    • 缺点:执行效率降低

面向过程编程

  • 将要解决的问题(功能需求)分解成具体的步骤,然后通过函数编程实现每一个步骤,最后通过函数规定好的顺序调用完成

  • 面向过程编程思想的优点

    • 能够针对步骤拆分,进行模块化封装(函数)
    • 可以实现代码复用,从而节省开发成本
  • 面向过程编程思想的缺点

    • 不够灵活维护,流程一旦确定就必须按照既定方式执行到底。

小结

1、计算机编程从对开发人员要求极高到要求不高,是一代代人坚持不懈的结果

2、面向对象编程是目前最为符合人类思维逻辑的一种编程思想

一、面向对象编程思想

目标:理解面向对象编程思想与面向过程编程思想的区别,了解面向对象编程思想的核心,建立面向对象编程思想

概念

面向对象编程思想:面向对象编程也叫做OOP编程(Objected Oriented Programming),是一种基于面向过程的开发思想。与面向过程强调分解事务步骤相似,面向对象更需要追求事务操作的“主体”,也就是对象

  • 面向对象编程是一种编程思想,不是一种具体技术

  • 面向对象是在面向过程基础之上发展而来,因此也是一种模块化编程思想(有函数)

  • 面向对象能够更加方便的实现代码的重复利用(适用于大型项目)

  • 在面向对象思维中,任何动作的执行或者数据都属于对象(一切皆对象)

原理

1、面向过程编程思想原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wMY9u1aC-1586445036390)(效果图\面向过程编程思想原理.gif)]

2、面向对象编程思想原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1JfUgG9X-1586445036396)(效果图\面向对象编程思想原理.gif)]

小结

  1. 面向对象编程是一种编程思想,与技术无关
  2. 面向对象编程的本质是增加数据和功能的操作主体,即对象
  3. 面向对象中所有的数据和功能都是由主体(对象)来调用和操作

二、面向对象基础

学习目标:掌握面向对象的基础语法,能够使用面向对象思想和语法来解决需求问题

  • 面向对象关键字
  • 类的定义、实例化和对象
  • 类成员
  • 访问修饰限定符
  • 内部对象$this
  • 面向对象开发规范
  • 魔术方法
  • 成员访问
  • 静态成员
  • 自动加载
  • 克隆
  • 综合运用

示例

1、面向过程的方式实现一个功能:购买商品

# 定义函数购买商品
function buy($goods_id,$num = 1){
    echo '商品:' . $goods_id . '购买:' . $num . '个!';
    return ;
}

# 调用解决
buy(1,10);	# 输出: 商品:1购买10个

2、面向对象的方式实现一个功能:购买商品

# 确定是消费者购买(类)
class Buyer{
    # 拥有购买功能(方法)
    function buy($id,$num = 1){
        echo '商品:' . $goods_id . '购买:' . $num . '个!';
    	return ;
    }
}

# 确定具体买家购买
$b = new Buyer();	# 产生具体买家(对象)
$b->buy(1,10);		# 输出: 商品:1购买10个

小结

1、面向对象是一种编程思想,编程语言要实现这种编程思想就会有一些相应的语法格式出现

2、使用面向对象语法格式实现的功能才属于面向对象编程(OOP)

1、面向对象关键字说明

目标:了解面向对象编程中一些关键字的意义

概念

面向对象关键字:基于面向对象开发时,所用到的一些关键字,用来表明不同的结构或者类型

  • 类:class,是定义面向对象主体的最外层结构,用来包裹主体的数据和功能(函数)。类是一类具有共性事务的代表,代表的是事务的共性。

  • 对象:object,是某类事务的具体代表,也是实际数据和功能操作的具体单元,也被称之为实例(instance)

  • 实例化:new,从一个抽象的概念得到一个符合抽象概念的具体实例的过程

  • 类成员:member,指类class结构中的所有内容,类成员里有三种

    • 方法:method,本质是在类class结构中创建的函数,也称之为成员方法或者成员函数
    • 属性:property,本质是在类class结构中创建的变量,也称之为成员变量
    • 类常量:const,本质是在类class结构中创建的常量

小结

1、因为面向对象思想的出现,会多出一些结构语法关键字

2、了解关键字的作用后,才能更灵活的应用关键字实现面向对象编程

2、面向对象简单技术实现

目标:掌握类、对象和实例化之间的关系

概念

:根据对象分析后得到的一种通用结构(分类)

  • class关键字声明类
  • 类名:自定义名字,通常首字母大写,一般多单词组成类使用驼峰法(大驼峰法)
  • 大括号:类内部的结构(member,类成员)
class 类名{
    
}
  • 实例化:类产生对象的过程
new 类名;
new 类名();	# 使用较多
  • 对象:根据类产生的某个具体存在的实体(instance),对象一般使用变量保存
$object = new 类名();

步骤

1、根据需求产生类结构(class)

  • 分析类拥有的数据
  • 分析类的行为

2、在需要使用对象的地方,对类进行实例化(new),并保存对象

示例

1、定义类基本语法:class 类名{}

# 定义一个空类
class Nothing{
}

2、类实例化产生对象:new

# 实例化,并将产生的对象保存在变量中
$n = new Nothing();
# 打印对象
var_dump($n);

# 打印结果分析
object(Nothing)#1 (0) { } 
object:对象
(Nothing):所属类名 
#1:对象编号,与类无关,是整个脚本中对象的序号,从1开始
(0):成员变量(属性)个数   
{}:具体成员变量信息(键值对)

3、类class是一种结构,如果写好没有语法错误的情况下,代码不会执行(与函数定义一样),也无法打印输出

# 直接打印类名
var_dump(Nothing);		# 错误,提示未定义的常量

4、类的命名规范:类的命名规范与函数类似,区别在于人为的通常会将类名的首字母大写

# 有效类名
class My1{}
class My_1{}
class _My1{}

# 无效类名
class 1My{}
class 1_my{}

5、如果碰到多单词组成的类名,通常使用驼峰法

class MyClass{}

小结

  1. 通过class关键字 + 类名 +{}创建类
  2. 类是一种结构,不会自动运行,也不能输出
  3. 通过new 类名实例化对象得到类的具体对象
  4. 可以通过new实例化无限个对象

3、类成员

目标:了解类成员的类型,类成员的作用,能够运用类成员去创建有效类

概念

类成员:指直接定义在类结构{}内部的一级成员,即直接依赖{}的成员

  • 类成员分类
    • 成员变量(属性):给对象存储数据的变量
    • 成员函数(方法):给对象调用解决问题的函数
    • 类常量:属于类内部的常量,使用const关键字定义
  • 属性和方法需要使用访问修饰限定符修饰,姑且使用public修饰
class 类名{
    # 类常量(可以多个)
    const 常量名 =;
    # 属性(可以多个)
    public $属性名 [ =];	# 可以赋值也可以不赋值,只声明
    # 方法(可以多个)
    [public] function 方法名([形参列表]){
        # 方法体(返回值)
    }
}
  • 成员访问:属性和方法都属于对象访问,类常量属于类访问(后续再讲)
    • 对象访问属性和方法,使用->
# 实例化
$object = new 类名();
# 属性访问
$object->属性名;		# 此时不带属性本身的$符号(前面保存对象的变量带$符号,object->属性名是整体)
# 方法访问
$object->方法名([实参列表]);

步骤

1、声明类结构

2、明确类产生的对象是否需要有数据的存储:确定属性

3、明确类产生的对象是否需要函数实现功能:确定方法

4、明确类是否需要定义常量:确定类常量

5、对象实例化

6、类成员访问(属性和方法)

示例

1、声明类结构

# 定义买家类:买家有姓名,有钱
class Buyer{
    # 属性声明
	$name;				# 错误,类内部属性必须使用访问修饰限定符
    public $name;		# 正确:没有赋值
    public $money = 0;	# 正确:有赋值
    
    # 方法声明
    function display(){
        echo __CLASS__;	# 魔术常量,输出类名
    }
    
    # 类常量声明
    const BIG_NAME = 'BUYER';
}

2、成员变量访问(属性和方法):成员必须通过对象才能进行访问,需要先通过实例化得到对象,然后通过对象实现对成员进行操作

# 实例化对象
$b = new Buyer();
# 访问属性
echo $b->money;
# 修改属性
$b->money = 1000;
# 删除属性
unset($b->name);
# 新增属性
$b->age = 20;

# 访问方法
$b->display();

注意

  • 删除属性和新增属性通常使用较少,更多的属性操作是访问和修改

  • 类常量不是由对象来进行访问,所以暂时不做访问讲解,到后面知识再讲

  • 属性和方法的使用都必须确保类中已经定义(属性可以新增)

3、类成员中:属性、类常量和方法都可以无限定义,但是定义的原则是相关性。除了以上三个类成员,不能在类结构{}中直接写其他任何代码

class Buyer{
    echo __CLASS__;					# 错误
    define('PI',3.14);	 			# 错误
    if(true){ echo 'hello world'}	# 错误
}    

小结

1、PHP类结构中有且仅有三种成员:属性、方法和类常量,其他直接属于类的内容都会报错

2、类声明需要根据业务来确定类成员的存在和数量

3、类中属性和方法的访问方式都是通过对象来调用:$对象->属性名/方法名();注意属性名不带$符号

4、类中定义属性不能直接属性名,需要使用符号public修饰(访问修饰限定符中的一种)

4、访问修饰限定符

目标:理解访问修饰限定符的作用,掌握访问修饰限定符的实际运用

概念

访问修饰限定符:用在属性或者方法前的修饰关键字,是用来控制属性或者方法的访问位置

  • 访问修饰限定符分类
    • public:公有,类内和类外都可以访问
    • protected:受保护,只允许在相关类内部访问
    • private:私有,只允许在定义类内部访问
  • 属性必须有访问修饰限定符,方法可以没有访问修饰限定符(默认public)

步骤

1、声明类结构

2、确定类成员

3、确定类成员的访问位置限定,使用对应访问修饰限定符

4、只能在对应位置访问被修饰的成员

示例

1、公有成员访问

class Saler{
    # 属性
    public $count = 100;
    # 方法
    public function getCount(){
        echo __METHOD__;		# 魔术常量,显示当前方法名(包含类名)
    }
    
    function setCount(){
        echo __METHOD__;
    }
}

# 实例化对象
$s = new Saler();

# 访问(类外)
echo $s->count;
$s->getCount();
$s->setCount();

2、受保护和私有成员设定(当前受保护和私有一致,需要高阶知识才会有区别)

class Buyer{
    # 属性
    protected $money = 10;
    private $account = '6226000000000001';
    
    # 方法
    protected function getMoney(){
        echo __METHOD__;
    }
    
    private function getAccount(){
        echo __METHOD__;
    }
}

# 实例化
$b = new Buyer();
echo $b->money;		# 错误:当前属于类外部,不能访问
echo $b->account;	# 错误:当前属于类外部,不能访问

$b->getMoney();		# 错误:当前属于类外部,不能访问
$b->getAccount();	# 错误:当前属于类外部,不能访问

总结

1、访问修饰限定符分为三种:public、protected和private,访问权限依次降低(类对成员控制权限依次增加)

2、访问修饰限定符限定的是成员到底在哪里能被访问,私有和受保护都只能在类内部访问,公有可以在任何地方访问(但都必须是对象去访问)

3、属性必须写清楚访问修饰限定符,方法可以省去(不建议),因为系统默认是public

5、类内部对象

目标:理解内部对象的概念,掌握内部对象对成员的访问

概念

内部对象:$this,方法内部内置的一个对象,会自动指向来调用方法的对象

  • $this存在于方法内部(仅限内部使用),所以相当于在类的结构内部
    • 可以访问任意访问修饰限定符修饰的成员
    • 私有成员都是通过公有方法来实现访问(公有方法可以在类外部访问)
  • 类内部对类成员的访问也需要通过对象才能访问,所以必须通过$this内部对象访问类成员

步骤

1、声明类结构

2、明确私有成员(不限定成员的访问修饰限定符)

3、私有成员需要在某种情况下被访问:增加方法,在方法里使用$this访问

示例

1、尝试在类内部方法中访问属性

class Saler{
  	# 属性
  	public $count = 100;
  	protected $discount = 0.8;
  	private $money = 100;
    
    public function getAll(){
        echo $count,$discount,$money;	# 全部错误:提示未定义的“变量”
    }					
}
$s = new Saler();
$s->getAll();

注意:方法本质是定义在类内部的函数,因此受制于作用域的问题,在方法内部访问的变量系统认定为局部变量(必须内部定义或者参数传入),否则就会提示未定义

2、类内部访问类成员,需要通过对象来进行访问

class Saler{
  	# 属性
  	public $count = 100;
  	protected $discount = 0.8;
  	private $money = 100;
    
    public function getAll(){
        # 需要获取到对象名字:因为方法本身就是函数,访问外部全局变量可以通过global引入实现
        global $s;
        echo $s->count,$s->discount,$s->money;		#正确输出
    }					
}

$s = new Saler();
$s->getAll();

注意:上述代码规定死了以后对象只能是$s,不能有其他对象或者其他命名,所以非常不友好

3、使用内置对象$this访问

class Saler{
  	# 属性
  	public $count = 100;
  	protected $discount = 0.8;
  	private $money = 100;
    
    public function getAll(){
        var_dump($this);
        echo $this->count,$this->discount,$this->money;		#正确输出
    }					
}

$s = new Saler();
$s->getAll();

注意:$this代表的是对象,而​$this所在环境为类内部的方法内部,所以$this对象是在类内部访问,因此可以访问所有的属性和方法,不受访问修饰限定符限制

$this、class和new之间的关系原理

  • class是定义类结构,属于非执行段代码,因此会被加载到代码段(编译阶段)

  • new是实例化对象,先判定类在内存(代码段)是否存在

    • 类不存在,报错;
    • 类存在,将类内部的属性部分复制一份,然后在内存(堆区)开辟一块内存空间,将属性放到里面,同时内部有一个指针指向类的内存空间(代码段)
    • 对象访问属性即访问的是对象空间里存储的部分
    • 对象访问方法是对象通过内部指针找到类空间中的方法,然后在内存(栈区)开辟运行
  • $this是系统在方法内置的对象通用名字

    • 对象在调用方法的时候,系统会自动找到对象所保存的内存地址(堆区),然后把地址赋值给$this
  • 方法内部的$this就代表调用当前$this所在方法的外部对象

    • $this的本质是函数内部的一个局部变量,只是系统自动对其进行赋值,而且一定是调用方法的对象本身
  • 面向对象编程代码运行内存关系

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mPdUnqSf-1586445036398)(效果图\面向对象编程代码运行内存关系.gif)]

小结

1、类内部方法内有一个内置对象$this,代表访问该方法的外部对象

2、类在实例化对象的时候要保证内存中有该类

3、一个类可以实例化多个对象,每个对象访问成员方法时,$this就代表对应对象

6、面向对象开发规范

目标:了解面向对象的基本开发规范,熟练运用开发规范实现项目开发

概念

开发规范:开发者约定俗成的开发设计方式

  • 属性的初始化
    • 属性是类对于同类事务所抽离出来的共性数据,本身在类结构中没有价值,是当具体对象产生之后,属于对象本身的
    • 进行类中定义属性的时候,通常不会对属性进行初始化,除非属性本身的值也具有共性
    • 属性如果没有初始化数据,那么在产生对象后应该对属性完成初始化(有的属性是在操作过程中被初始化)
  • 访问修饰限定符选择:访问修饰限定符是用来限制类成员被对象访问时对象所处位置的。访问的权限从public、protected到private依次变小(类对成员的控制权限依次变大)
    • 设定好的类成员本身不会被外部用到,那么应该使用private或者protected
    • 设定好的类成员一定会给外部访问,使用public
    • 属性通常private居多,如果需要外部访问属性,通常会定义相关方法来实现属性的查看和修改,因为可以在方法内对数据逻辑进行代码控制,安全
    • 尽可能增加类对成员的控制(尽可能多使用private,少使用public)

示例

1、属性初始化

class Saler{
  	# 属性
  	public $count;				
    # 某个卖家拥有的商品数量,每位具体卖家对象拥有的不可能一样,所以没必要初始化
  	protected $discount;
    # 某个卖家针对销售的折扣,同样没有统一的价值
  	private $money = 0;
    # 某个卖家的账户余额,任何一位卖家一开始做生意的时候,账户余额都为0,所以可以初始化
    
    # 业务初始化属性
    public function setDiscount($discount = 1){
        # 可以进行逻辑控制
        $this->discount = $discount;
    }
}

# 实例化对象,初始化属性
$s = new Saler();
$s->count = 100;

# 打折促销:业务初始化
$s->setDiscount(0.8);

2、访问修饰限定符选择

class Saler{
  	# 属性
  	public $count;				
  	private $money = 0;
    
    # 增加方法操作私有属性money
    public function getMoney(){
        return $this->money;
    }
    public function setMoney($money){
        # 可以对逻辑进行修改,对数据进行安全判定,保证数据的安全性
        $this->money = $money;
        # $this->money是属性,$money是外部传入的参数,二者同名但是性质完全不同
    }
}  

小结

1、属性在类中定义的时候,通常不会初始化值,除非所有类实例化得到的对象的某个属性需要是统一值

2、应该尽可能增加类对成员的控制,即使用范围较小的访问修饰限定符优先

3、属性通常是私有化的,一般是通过设定方法来实现属性的访问和修改

7、构造方法

目标:了解魔术方法的概念,掌握构造方法的作用,明确构造方法的触发模式

概念

构造方法:__construct(),是一种类结构特有的特殊方法,该方法由系统规定好,开发人员在定义的时候只需要一遍,有了构造方法的类在实例化对象之后,对象就会自动调用。

  • 构造方法是一种魔术方法:魔术方法是会自动被触发,不需要手动调用的方法
  • 构造方法的目标是为了实现对象的初始化
    • 对象实例化之后会自动调用
    • 构造方法通常是为了实现对象所需资源的初始化(属性、资源)
  • 构造方法虽然为魔术方法,但本质依然是一个方法
    • 受访问修饰限定符控制(对象的产生位置会发生改变)
    • 对象可以选择调用(一般不会)
  • 构造方法可以设定形参,形参对应的实参是在实例化对象的时候传入:new 类名(实参传递给形参)

步骤

1、确定类中需要有数据实现初始化,而且是灵活处理,每个对象都不一样的:使用构造方法

2、确定初始化的数据需要外部传入:使用构造方法设定形参

3、在构造方法内部利用内部对象实现初始化需求

  • 属性初始化
  • 资源初始化
  • 其他内置方法调用

4、实例化对象时必须传入构造方法所需数据

示例

1、构造方法实现:在类中增加一个方法__construct()即可

class Saler{
    # 构造方法
    public function __construct(){
        echo __CLASS__;
    }
}

2、构造方法也是一个方法,不普通的地方在于,类实例化得到的对象会马上自动调用

# 实例化
new Saler();				# 输出Saler

3、构造方法的意义:构造方法是对象实例化的时候用来初始化对象的资源的,所以通常是用来初始化对象的属性或者其他资源初始化

class Saler{
  	# 属性
  	public $count;				
  	private $money;
    
    # 构造方法:初始化属性
    public function __construct(){
        $this->count = 100;
        $this->money = 100;
    }
}

5、如果属性的数据在构造方法中初始化是固定写死的,那么与直接在定义类的时候初始化属性一样。意味着数据没有任何价值(所有对象都相同),因此通常是通过构造方法的参数来实现数据的外部传入

class Saler{
  	# 属性
  	public $count;				
  	private $money;
    
    # 构造方法:初始化属性
    public function __construct($count,$money){
        $this->count = $count;
        $this->money = $money;
    }
}

5、一旦构造方法拥有了形参,那么对象在调用该方法的时候就需要传入对应的实参,而构造方法又是自动调用的,所以需要在实例化对象的时候使用new 类名(构造方法对应的实参列表)来实现

# 实例化对象
$s1 = new Saler(100,100);
$s2 = new Saler(1000,1000);
$s3 = new Saler;			# 错误:因为此时类有构造方法且要求传入参数,所以必须使用()并传入数据

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

6、构造方法不管再怎么特殊,也是用户定义的方法,言外之意除了在实例化对象时对象会自动调用之外,我们也可以手动调用构造方法(但是一般没有价值,因为对象实例化时会自动调用)

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);			# 允许手动调用

小结

1、构造方法__construct()是一种系统内置的方法,该方法的特性是会在对象实例化之后,对象立即自动调用

2、构造方法的目的就是为了初始化资源,包含对象属性和其他资源

3、一旦构造方法定义好之后,且构造方法自带参数,那么就只能使用new 类名(参数列表)方式才能正确实例化

4、构造方法可以当做普通方法由对象调用(不建议)

8、析构方法

目标:了解析构方法的作用,能够实际应用析构方法

概念

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

  • 析构方法是用来对象销毁时主动释放资源的
  • 对象销毁
    • 对象无变量指向(变量指向其他数据)
    • 对象被主动销毁(unset销毁对象变量)
    • 脚本执行结束(自动释放资源)
  • PHP脚本执行结束会释放所有资源,所以一般较少使用析构方法

步骤

1、定义类结构

2、确定需要在对象销毁时释放资源

3、使用析构方法释放资源

示例

1、析构方法实现:类中增加一个__destruct()方法

class Saler{
  	# 析构方法
    public function __destruct(){
        echo __FUNCTION__;
    }
}

2、析构方法调用:析构方法是在对象被销毁时自动,对象的“垂死挣扎”

# 实例化对象
$s = new Saler();

# 对象变量指向其他数据
$s = 1;	

# 主动销毁对象变量
unset($s);			

# 脚本执行结束自动释放

3、析构方法也是普通方法,可以由对象直接调用

# 接析构方法实现代码
$s = new Saler();
$s->__destruct();		# 思考:此时对象是否被销毁?

小结

1、析构方法是一种对象销毁时自动调用的方法

2、析构方法是用来对象销毁自身所占用的资源

3、PHP中脚本执行结束,系统会自动回收所有资源,因此一般PHP中很少使用析构方法

9、对象传值

目标:了解PHP中对象传值的方式

概念

对象传值:将保存对象的变量赋值给另外一个变量

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

步骤

1、定义类结构

2、实例化产生对象,保存在变量中

3、将保存对象的变量赋值给另外一个变量

示例

1、对象传值就是保存对象的变量赋值给另外一个变量

class Saler{}
$s1 = new Saler();
$s2 = $s1;

2、对象传值是引用传递,不管对象赋值给多少个变量,内存中只有一个对象

# 证明
var_dump($s1,$s2);		# 同一个对象
$s1->name = 'Saler';	# 更改一个变量所保存对象的属性
echo $s2->name;			# 输出Saler

小结

1、对象传值方式是引用传值,不论对象如何被赋值给其他变量,始终只有一个对象

10、范围解析操作符(类常量访问)

目标:理解范围解析操作符的概念和目标,掌握范围解析操作符的应用

概念

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

  • 范围解析操作符是用于给类(类名)访问类成员使用的
类名::类成员
  • 范围解析操作符也可以被对象用来当做类使用(不建议使用)
$对象名::类成员
  • 类常量只能被类访问

步骤

1、定义类结构

2、确定成员需要由类进行管理:类常量

3、在需要访问类常量的时候使用范围解析操作符访问

示例

1、类常量的普通访问尝试:尝试使用对象进行访问

class Saler{
	# 类常量
    const PI = 3.14;
}
$s1 = new Saler();
echo $s1->PI;			# 错误,$s1->PI最终转换的访问方式为:$PI,这个在类中并不存在

2、以上案例可以看出,对象无法访问类常量,那是因为类常量的定义本身就是用来给类访问的,对象是用来访问属性和方法的,类常量的访问方式为:类名::常量名

# 类+范围解析操作符访问类常量
echo Saler::PI;			# 输出3.14

3、对象本身也依赖于类,因此也可以使用对象对类控制成员进行访问,需要使用范围解析操作符

$s = new Saler();
echo $s::PI;			# 输出3.14

注意:以上方式能访问,但是不建议使用(以上方式也能看出,成员谁来访问,关键看用什么符号:①使用范围解析操作符::就是类访问;②使用对象操作符号->就是对象访问)

4、分析:类常量是固定的,而对象的属性是不同对象而不同的,成员方法简单的理解也是为属性本身进行加工的。因此有一些东西是专属于类的,而有部分内容是专门为对象提供的,所以就会有不同的成员拥有不同的访问方式

小结

1、类访问成员的方式是使用范围解析操作符“::”访问,由类名直接访问:类名::类常量

2、类本身是通过对同类对象的抽象而形成,所以属性和方法本身都是由对象来访问

3、类也需要有一些自身的数据和操作,这些就由类来进行访问

11、静态成员

目标:理解静态成员的概念,掌握静态成员的性质以及访问方式

概念

静态成员:使用static关键字修饰的类成员,表示该成员属于类访问

  • PHP静态成员有两种
    • 静态属性
    • 静态方法
  • 静态成员是明确用来给类访问的,而不是对象
  • 静态成员只是多了一个static关键字修饰,本身也可以被对象访问
  • 静态成员同样可以使用不同访问修饰限定符限定,效果一致

步骤

1、定义类结构

2、确定有些成员(属性、方法)不需要对象访问,直接通过类访问

3、使用static关键字修饰

4、静态成员应该让类进行访问

示例

1、静态属性:在类中定义属性的时候使用static关键字修饰,访问的时候只能使用类+范围解析操作符+静态属性访问

class Saler{
	# 属性
    public $money = 0;
    public static $count = 0;	# 静态属性
}

# 静态成员可以直接使用类访问,而不需要先实例化对象
echo Saler::$count;

2、静态方法:在类中定义方法的时候使用static关键字修饰,访问的时候使用类+范围解析操作符+静态方法名字()访问

class Saler{
	# 方法
    public static function showClass(){
        echo __CLASS__;
    }
}

# 类直接访问
Saler::showClass();

3、在类的内部也可以访问静态成员,同样是使用类名+范围解析操作符+静态属性/静态方法()

class Saler{
    # 属性
    private static $count = 0;			# 私有,不允许外部直接访问
	# 方法
    public static function showClass(){
        echo Saler::$count;
    }
}

# 类直接访问
Saler::showClass();

4、静态方法本质也是类中定义的方法,因此也可以使用对象进行访问,但是不建议

# 对象访问静态方法
$s = new Saler();
$s->showClass();						# 输出0

5、同理,方法也是在类内部,在编译时就存在,因此可以通过类来进行访问,使用范围解析操作符,但是非常不建议(会报错:因为类只允许访问静态成员和类常量)

class Saler{
    public function testStatic(){
        echo __FUNCTION__;
    }
}

# 类访问普通成员方法
Saler::testStatic();				# 输出testStatic,但是报错,当前访问的不是静态方法

6、静态方法本质是给类访问,所以不允许在静态方法内部使用$this对象

class Saler{
    public static function testStaticThis(){
        var_dump($this);				# 致命错误:$this放到了不该放的位置
    }
}			

小结

1、为了保障类能直接访问数据和操作数据,可以在属性和方法前增加static关键字变成静态属性和静态方法

2、类通过类名+范围解析操作符+静态成员的方式进行访问

3、静态成员也受访问修饰限定符的限定,访问权限与普通属性和方法的限制一样

4、对象可以无条件访问静态方法,而类只能访问不带$this的普通方法(不建议)

5、静态成员是给类访问的,非静态成员是给对象访问的

  • 静态属性和方法(静态和非静态)都是保存在类结构中(代码段)
  • 普通属性保存在对象生成的对象空间里(堆)

6、静态成员的访问效率比非静态成员高,因此有种说法是能用静态的时候就不用非静态(对象的特点是多元化,而静态的特点是单一化)

12、self关键字

目标:了解self关键字的作用和应用场景

概念

self关键字:在类的内部(方法里面)使用,代替类名的写法

  • self如同$this代表内部对象一样,能够在方法内部代替当前类名

  • 能够保障用户方便修改类名字

  • self关键字是代替类名,所以需要配合范围解析操作符::

步骤

1、定义类结构

2、方法内部需要使用类名来进行成员访问(类常量、静态成员)

3、使用self关键字代替类名

示例

1、self是用来代替类名的,与范围解析操作符::一起使用的

class Saler{
    # 静态属性
    private static $count = 0;			# 私有,不允许外部直接访问
	# 静态方法
    public static function showClass(){
        echo Saler::$count;
        echo self::$count;				# 代替类名
    }
}

2、self也可以在类的内部方便实例化对象:比如构造方法被私有化之后,就没有办法在类外部实例化对象,此时可以在类内部进行对象实例化

class Saler{
    # 属性
    private static $count = 0;			# 私有,不允许外部直接访问
    private function __construct(){}	# 私有,不允许外部实例化(因为对象不能外部调用)
	# 方法
    public static function getInstance(){
        return new Saler();				# 使用类名实例化
        return new self();				# 使用self关键字实例化
    }
}

$s = Saler::getInstance();

小结

1、self是一种在类内部用来代替类名的关键字

2、self可以用来在类内部访问静态成员

3、self也可以在类内部用来实例化对象

4、帮助类名修改时,不用修改任何类的内部结构

13、类的加载

目标:理解类的使用机制,掌握类的加载方式和原理

概念

类的加载:类的访问必须保证类在内存中已经存在,所以需要在用类之前将类所在的PHP文件加载到内存

  • 类的加载分为两种

    • 手动加载:在需要使用类之间通过include将包含类的文件引入到内存
    • 自动加载:提前定义好类结构和位置,写好引入类文件代码,在系统需要类而内存不存在的时候想办法让写好的加载类的代码执行(自动加载是自动运行写好的加载类的代码)
  • 真实开发中因为类文件很多,所以通常都会使用自动加载来节省开发工作量

  • 自动加载有两种方式都可以实现

    • 魔术函数__autoload():系统自动调用,需要传入类名,在函数内部实现类的手动加载
    function __autoload($classname){
        # 找到对应的文件路径和命名规范,手动加载
    }
    
    • 自定义函数:自己定义类的加载实现,然后通过spl_autoload_register注册到自动加载机制(可以注册多个自动加载)
    # 自定义类加载函数
    function 自定义函数($classname){
        # 找到对应的文件路径和命名规范,手动加载
    }
    
    # 注册自动加载
    spl_autoload_register('自定义函数名字');
    
  • 自动加载要求在声明类的时候有良好的规范

    • 类名与文件名一致:类名.php(现在较多使用)或者类名.class.php
    • 类文件分类放好

步骤

1、定义类文件

  • 类文件是独立文件,除了类之外不包含其他代码
  • 类文件通常是一个类一个文件

2、确定加载类型,写好加载代码

  • 手动加载:主动在调用前手动加载即可

  • 自动加载__autoload():实现好自动加载函数,在使用类之前声明好

  • 自动加载spl_autoload_register():自定义加载函数,在使用类之前通过spl注册

3、在明确类能够通过自己写的加载代码加载时,可以直接使用类

  • 能找到:实现类的加载和使用
  • 找不到:报错类无法找到

示例

1、手动加载:即要访问某个类之前,使用文件包含将类所在的文件加载进来

# 类文件:Saler.php
<?php
class Saler{}
?>
    
应用文件:useSaler.php
# 使用Saler类需要先包含Saler类所在的文件
include_once 'Saler.php';				# 通常使用include_once,因为类不允许重名
$s = new Saler();

2、加载类文件是一种比较消耗资源的方式,所以有的时候不确定类是否在内存中存在,可以事先使用class_exists()函数来判定是否存在,存在就不用加载,不存在才加载

# 使用Saler类,但是不确定内存中是否存在
if(!class_exists('Saler')){
	# 不存在:加载
    include_once 'Saler.php';
}

# 使用
$s = new Saler();

3、自动加载:PHP没有那么智能的系统自动加载,所谓自动加载只是PHP提供了一种加载机制:即实现定义一个函数__autoload(),然后当系统需要使用类,而内存中又不存在的时候,系统就会自动调用__autoload()函数来加载类文件.

# 自动加载机制:利用系统提供的__autoload()函数
function __autoload($classname){			# 参数为类名:即当前需要访问的类的名字
	# 需要人为定义去哪加载,怎么加载   
    include_once $classname . '.php';		# 假定为当前目录下,类文件名字为:类名.php
}

# 使用类:内存目前并没有
$s = new Saler();							# 系统发现内存没有Saler,所以调用__autoload()去加载

4、一个系统里,可能类文件会放到不同的路径下,因此一个完整的自动加载函数,应该要进行文件判定以及加载功能

# 定义自动加载
function __autoload($classname){
	# 组织文件路径:假设当前路径下,有两个文件夹下都有类c和m
    $c_file = 'c/' . $classname . '.php';		# 如c/Saler.php
    if(file_exists($c_file)){
        include_once $c_file;
        return true;
    }
    
    # 说明c文件夹没有对应的文件
    $m_file = 'm/' . $classname . '.php';		# 如m/Saler.php
    if(file_exists($m_file)){
        include_once $m_file;
        return true;
    }
}

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

5、随着PHP版本的提升,在7以后,不怎么建议直接使用__autoload()函数,而是采用一种注册机制,将用户自定义的函数,放到系统内部,使用spl_autoload_register(定义好的函数)。本质与__autoload()一样

# 定义一个函数,用来加载类文件
function my_autoload($classname){		# 也需要一个参数来接收要加载的类名字
    # 功能与__autoload()一样
    $c_file = 'c/' . $classname . '.php';		# 如c/Saler.php
    if(file_exists($c_file)){
        include_once $c_file;
        return true;
    }
    
    # 说明c文件夹没有对应的文件
    $m_file = 'm/' . $classname . '.php';		# 如m/Saler.php
    if(file_exists($m_file)){
        include_once $m_file;
        return true;
    }
}

# 此时,上述函数永远不会自动运行,除非将函数注册到系统内部
spl_autoload_register('my_autoload');

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

# c路径加载
function c_autoload($classname){		
    $c_file = 'c/' . $classname . '.php';		
    if(file_exists($c_file)){
        include_once $c_file;
    }
}

# m路径加载
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');    

小结

1、类的使用必须先保证内存中该类存在

2、可以使用手动加载来确保类的使用安全:优点是明确,缺点是繁琐

3、可以使用自动加载来让系统按照开发者设定的路径和方式去寻找类,并尝试加载到内存

4、自动加载可以使用__autoload()函数来实现,也可以使用自定义函数+spl_autoload_register()注册共同实现(后者推荐)

  • 优点:代码简介(基本上属于一次性定义)
  • 缺点:给人感觉不可控

5、基本上所有的框架都在使用自动加载机制

14、对象克隆

目标:了解对象克隆的概念,掌握克隆魔术方法的意义

概念

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

  • 克隆对象与原来对象内容一致(表象)
  • 克隆出来的对象是新对象
  • 对象克隆出来后会自动调用魔术方法__clone()(如果有该方法)

步骤

1、定义类时考虑对象是否允许被克隆,以及允许克隆后是否需要针对克隆对象做操作

  • 不允许克隆:私有化__clone()魔术方法(不允许外部克隆,使用者一般都是外部)
  • 允许克隆处理:在__clone()方法中设定好克隆对象的处理

2、实例化对象并保存到变量

3、需要从已有对象产生新对象(不是赋值)

4、使用clone产生对象

示例

1、对象克隆是通过clone关键字实现,即:clone 对象;

class Saler{
  	# 属性
  	public $count;				
  	private $money;
}
# 实例化
$s1 = new Saler();
$s1->count = 1;

# 克隆
$s2 = clone $s1;

2、克隆出来的对象与原来对象是两个内存地址,因此是两个不同的对象

# 接上述代码
$s2->count = 2;

echo $s1->count;		# 1,没有变化

3、对象在实例化的时候会自动调用存在的构造方法__construct(),同样的,在类的内部,PHP允许定义一个__clone()的方法,在对象被克隆后,新克隆出来的对象会自动调用

class Saler{
  	# 属性
  	public $count;				
  	private $money;
    # 克隆方法
    public function __clone(){
        var_dump($this);			# 编号为2,代表是克隆出来的对象
        $this->count++;
    }
}
# 实例化
$s1 = new Saler();
$s1->count = 1;

# 克隆
$s2 = clone $s1;

4、如果不允许对象被克隆,可以将__clone()方法私有化(本质是不允许对象在外部被克隆)

class Saler{
  	# 属性
  	public $count;				
  	private $money;
    # 私有化克隆方法
    private function __clone(){}
}
# 实例化
$s1 = new Saler();
$s1->count = 1;

# 克隆
$s2 = clone $s1;			# 致命错误:不允许对象在外部访问一个私有方法

小结

1、对象可以通过克隆来得到新的对象(以前只有实例化)

2、克隆出来的对象会自动调用类中对应的__clone()方法(如果有)

3、因为克隆对象是新对象,不会改变原来的对象,如果需要一个和当前对象一致状态的对象,但是又不想改变当前对象的状态,那么就可以通过克隆来实现

4、可以通过私有化克隆方法来实现禁止外部对象克隆

15、总结

1、面向对象编程核心是“万物皆对象”的思想,与面向过程编程思想的本质区别是事务操作的主体:每一次操作都是由明确的对象来执行

2、面向对象思想的本质是将数据(属性)、数据操作(函数)进行的一次二次封装(类),而往后的所有操作都必须由类或者类产生的对象进行调用

3、面向对象核心关键字

  • 类:class,结构主体,某一类相似主体的公共部分抽离

  • 类成员:类结构中能够直接定义的成员

    • 属性(property):存储数据的变量
    • 方法(method):数据操作的逻辑
    • 类常量(const):存储固定数据的内部常量(const定义)
  • 实例化:new,类结构产生对象的过程

  • 实例:instance,一个具体的对象

4、访问修饰限定符:限定被修饰成员的方位位置(所有类成员都能被访问)

  • public:公有,表示不限定访问区域
  • protected:受保护,表示限定范围为关联类内部(本质是方法内部,关联类需要学习继承)
  • private:私有,表示限定范围为自己类内部(内部方法内)

5、类内部对象:$this

  • 普通方法内部,代表来访对象
  • 因为在类内部,所以不论任何访问修饰限定符限定,都可以访问

6、魔术方法:自动调用的方法

  • 构造方法:__construct(),对象初始化资源,对象实例化后自动调用
  • 析构方法:__destruct(),对象销毁资源,销毁时对象自动调用
  • 克隆方法:__clone(),对象被克隆后,克隆对象自动调用

7、对象传值:对象是引用传值,不会产生新对象

8、范围解析操作符:::,用于类直接访问成员的符号

  • 类访问类专属成员:类常量(静态属性和静态方法)
  • 对象也可以使用该符号访问类成员(不建议)

9、静态成员:使用static关键字修饰的成员,用来给类访问的成员(对象也能访问)

  • 静态成员:静态属性和静态方法
  • 静态成员在编译(类加载到内存)时初始化,不用产生对象即可访问(效率高)
  • 静态属性不会出现在对象的内存空间

10、self关键字:类内部用来代表类名访问类成员的

  • self与范围解析操作符结合访问:self::类成员
  • self可以代替类名在类内部进行实例化:new self()

11、类的加载:类的使用的前提是类在内存中必须存在

  • 手动加载:使用前写好加载路径
  • 自动加载:在内存找不到要用的类的时候自动执行
    • __autoload()函数:魔术函数,在函数内部写好加载方式
    • 自定义函数+spl_autoload_register()函数:与魔术函数类似(建议使用)

12、对象克隆:从已有对象产生新对象

  • 防止克隆:私有化话克隆魔术方法

13、多关注业务,懂得如何根据业务划分类,然后慢慢掌握类内部应该如何定义成员去解决需求问题,从而实现面向对象思想基本语法的使用

16、封装数据库操作类

目标:理解类的封装的概念,掌握类的封装过程,掌握类成员的实际应用以及类对成员的控制关系

概念

封装数据库操作类:因为目前所使用的mysqli扩展实现数据库的操作还比较零散,如果想要高效的使用,就必须要进行二次加工

  • 在面向对象编程中,所有操作都应该是由类来实现完成
  • 封装的完整程度是根据业务的需求来定

步骤

1、确定要封装的业务:基于mysqli的数据库底层实现,完成数据库操作的基本诉求

  • 简化初始化操作
  • 实现错误处理
  • 实现增删改查

2、明确封装的类的作用,确定内部实现机制

  • 方法独立性:一个方法只做一件事情
  • 方法独立性:方法只负责执行,不对结果进行任何处理,交由外部调用处判定
  • 灵活性:封装的所有操作应该是灵活的,不是写死的内容

3、根据思路明确封装类的数据和数据操作

  • 数据使用属性保留:数据需要跨方法或者对外提供数据支持
    • 数据库操作的数据:主机地址、端口、用户名、密码、数据库名字、字符集
    • 数据库连接资源跨方法:连接资源
    • 错误信息对外数据支持:错误信息、错误编号
  • 数据操作具体功能
    • 初始化资源工作:构造方法,实现属性初始化
    • 初始化数据库资源:实现数据库的连接认证、字符集设置和数据库选择:失败返回false并记录错误
    • SQL指令语法检查:SQL执行并完成错误处理:失败返回false并记录错误
    • 写操作:实现增伤改指令的执行:调用SQL指令语法检查,成功返回受影响行数
    • 自增长ID获取:实现自增长id获取
    • 读操作:单记录获取和多记录获取:调用SQL指令语法检查

4、确定类的控制

  • 不需要外部访问和使用的私有
  • 明确外部需要用到的公有
  • 如果数据安全性要求高,那么可以属性私有,但是允许对外提供可以操作的公有方法(内部安全处理)

示例

1、一个类通常就是一个文件,所以要先确定文件的名字:通常类文件命名规范有两种

  • 文件名字与类名字一样,如Sql.php
  • 为了区分普通PHP文件,增加中间类描述,如Sql.class.php
  • 现在PHP几乎都是面向对象编程,所以通常采用第一种方式:因此当前命名数据类的文件为:Sql.php

2、确定类文件名字后其实也就确定了类名字,因此可以创建一个Sql类

# 数据库操作类
class Sql{}

3、类的创建分两种:一是特定使用,即类里面的所有内容只为某次使用;二是通用,即工具类,以后很多地方可以用。

  • 特定使用,功能可以不用太灵活
  • 通用工具,功能应该大众化,数据的变化会比较多

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

4、数据库的操作最基本的特性不会改变:即需要连接认证,而连接认证的信息是灵活的,所以可以通过设定属性来控制,这些信息也都是不同使用者不同的,应该可以改变,所以可以通过构造方法来实现数据传入

# 数据库操作类
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' => 'blog',
    		'charset' => 'utf8'
    	)
    */
    public function __construct(array $info = array()){
        # 初始化:确保用户传入了数据,否则使用默认值
        $this->host = $info['host'] ?? 'localhost';
        $this->port = $info['port'] ?? '3306';
        $this->user = $info['user'] ?? 'root';
        $this->pass = $info['pass'] ?? 'root';
        $this->dbname = $info['dbname'] ?? 'test';
        $this->charset = $info['charset'] ?? 'utf8';
    }
}

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

5、数据库属性会在实例化Sql对象的时候自动初始化

# 接上述代码(类外测试)
$s1 = new Sql();			# 使用默认数据库信息
$db = array(
	'host' => '192.168.0.1',
	'user' => 'admin',
	'pass' => 'admin',
	'dbname' => 'Taobao'
);
$s2 = new Sql($db);			# 使用外部数据库信息

6、数据库要操作的第一件事就是连接认证,所以需要一个连接认证的功能。这里可以使用mysqli面向过程的方法。但是需要建立一个方法来实现连接认证:连接是否成功?

# 对外提供属性,记录错误数据(外部如何处理,不需要我们管)
public $errno;
public $error;

# mysqli的连接资源对象是任何mysqli扩展操作的基础,因此需要该连接对象能够在其他方法中使用:属性处理
public $link;

# 在上述类中增加一个方法:实现连接认证功能
public function connect(){
	# 利用属性可以跨方法访问:5个参数分别为:主机、用户名、密码、数据库、端口
    # 利用错误抑制符抑制可能出现的错误
	$this->link = @mysqli_connect($this->host,$this->user,$this->pass,$this->dbname,$this->port);
    # 判定连接是否成功
	if(!$this->link){
        # 将错误信息保存到记录错误的属性中,返回false
        $this->errno = mysqli_connect_errno();
        $this->error = mysqli_connect_error();
        return false;
	}
    
    # 返回一个连接结果:不需要返回资源对象,为真即可表示成功
    return true;
}

7、连接认证包括数据库选择设定好后,此时还存在一个细节问题:字符集,为了保证数据库连接的正常操作,需要新增一个方法设定字符集

# 在Sql类中增加设定字符集的方法
public function charset(){
    # 调用mysqli的设置字符集的函数
    $res = mysqli_set_charset($this->link,$this->charset);
    
    # 判定是否成功
	if(!$res){
		$this->errno = mysqli_errno($this->link);
		$this->error = mysqli_error($this->link);
		return false;
	}
    
    return true;
}

8、初始化完成后,可以实现具体的业务处理:所有的SQL都需要使用mysqli_query执行,也都可能产生错误,因此封装一个专门执行SQL并检查错误的方法

# SQL执行以及错误检查
public function check($sql){
	# 执行SQL
	$res = mysqli_query($this->link,$sql);

	# 判定结果
	if(!$res){
		$this->errno = mysqli_errno($this->link);
		$this->error = mysqli_error($this->link);
		return false;
	}

	# 返回正确结果
	return $res;
}

9、上述功能本质也可以是一个写操作(不完整),但是写操作是有业务性的:返回受影响的行数,因此独立增加一个写操作方法,调用上述方法实现,并根据结果返回受影响的行数

# 写操作
public function write($sql){
	# 调用方法检查执行
	$res = $this->check($sql);

	# 判定执行结果:成功返回受影响的行数,失败返回false,错误已经在check方法中记录
	return $res ? mysqli_affected_rows($this->link) : false;
}

10、写操作中可能会有新增需求,因此也对外提供一个获取自增长Id的方法

# 自增长id
public function insert_id(){
	return mysql_insert_id($this->link);
}

11、读取操作:读取一条记录(利用check进行SQL执行和错误检查):读取一条数据可能需要获取当前查询结果的列数,增加属性保留

# 属性:记录查询结果中的列数
public $columns = 0;
    
# 读操作
public function read_one($sql){
	# 执行SQL错误检查
	$res = $this->check($sql);
    
    # 读取记录列数
    $this->columns = @mysqli_field_count($this->link);

	# 判定结果,进行加工:成功读取一条记录,失败返回错误信息
	return $res ? mysqli_fetch_assoc($res) : $res;
}

12、读取操作:读取多条记录:可能需要知道总的记录数以及查询结果的列数

# 属性:记录查询结果的行数
public $rows = 0;

# 读操作
public function read_all($sql){
	# 执行SQL错误检查
	$res = $this->check($sql);

	# 判定结果,进行加工
	if(!$res) return $res;
    
    # 记录结果数量
    $this->rows = mysqli_num_rows($res); 

	# 根据需求解析数据
	$list = [];
	while($row = mysqli_fetch_assoc($res)) $list[] = $row;

	# 返回结果
	return $list;
}

13、用户在使用Sql类的时候,必须要进行第一步实例化、然后连接认证和实现字符集设置。这样的话用户操作比较麻烦,应该是用户实例化Sql类就可以直接进行相应的业务处理:所以可以将连接认证、字符集设置在初始化方法中实现(构造方法)

public function __construct(array $info = array()){
    # 初始化:确保用户传入了数据,否则使用默认值
    $this->host = $info['host'] ?? 'localhost';
    $this->port = $info['port'] ?? '3306';
    $this->user = $info['user'] ?? 'root';
    $this->pass = $info['pass'] ?? 'root';
    $this->dbname = $info['dbname'] ?? 'test';
    $this->charset = $info['charset'] ?? 'utf8';
    
    # 调用初始化和字符集设置
    if(!$this->connect()) return;
    $this->charset();
}

14、确定类的控制:不需要外部访问的私有,需要外部访问的公有,重要的数据私有并增加公有操作方法进行安全控制

# 数据库初始化资源私有:不需要外部访问
private $host;
private $port;
private $user;
private $pass;
private $dbname;
private $charset;

# 连接资源仅限内部使用
private $link;

# 连接认证和字符集设置已经内部调用,不需要外部使用
private function connect(){}
private function charset(){}

# SQL检查属于内部调用,不需要公有
private function check($sql){}

14、测试:利用数据库类实现数据库的写操作和读操作

小结

1、类的封装是以功能驱动为前提,相关操作存放到一个类中

2、一个类通常是一个独立的文件,文件名与类名相同(方便后期维护和自动加载)

3、类中如果有数据需要管理,设定属性

4、类中如果有功能需要实现(数据加工),设定方法

5、一个功能通常使用一个方法实现,方法的颗粒度应该尽可能小(方便复用)

6、应该尽可能增加类对成员的控制:即能私有尽可能私有

7、类中需要实现的功能应该由具体的业务来实现支撑

  • 实用类:只考虑当前业务,不考虑全面性(代码少,应用范围小)
  • 工具类:全面综合考虑,尽可能多的封装可能存在的业务(代码多,应用范围广)

猜你喜欢

转载自blog.csdn.net/qq_36618918/article/details/105422975