PHP面向对象基础
引入:计算机编程在历史的发展长河中,经历了多次
版本变革
,变化的轨迹是伴随着硬件的发展和人们对于计算机的认知以及需求。
机器语言:即开发者(科学家)使用
0
和1
组成命令,然后在特定计算机上执行优点:执行效率高
缺点:开发难度大、移植性差、开发成本高
汇编语言:开发者使用简洁
英文字母
和符号
组成,让计算机读取后根据符号进行加工执行- 优点:指令简单明了、推广性高
- 缺点:移植性差、功能简单
高级计算机语言:开发者使用类似自然语言的
符号
组成,高级语言根据编程思想分为面向过程编程
和面向对象编程
两种,然后系统对程序代码进行编译(需要第三方编译器)然后执行- 优点:移植性强、可读性强、推广性非常高
- 缺点:执行效率降低
面向过程编程
将要解决的问题(功能需求)分解成具体的步骤,然后通过函数编程实现每一个步骤,最后通过函数规定好的顺序调用完成
面向过程编程思想的优点
- 能够针对步骤拆分,进行模块化封装(函数)
- 可以实现代码复用,从而节省开发成本
面向过程编程思想的缺点
- 不够灵活维护,流程一旦确定就必须按照既定方式执行到底。
小结
1、计算机编程从对开发人员要求极高到要求不高,是一代代人坚持不懈的结果
2、面向对象编程是目前最为符合人类思维逻辑的一种编程思想
一、面向对象编程思想
面向对象编程思想概念及原理
定义:面向对象编程也叫做OOP编程(Objected Oriented Programming),是一种基于面向过程的开发思想。与面向过程强调分解事务步骤相似,面向对象更需要追求事务操作的“主体”,也就是
对象
- 面向对象编程是一种编程思想,不是一种具体技术
- 面向对象是在面向过程基础之上发展而来,因此也是一种模块化编程思想(有函数)
- 面向对象能够更加方便的实现代码的重复利用(适用于大型项目)
- 在面向对象思维中,任何动作的执行或者数据都属于对象(一切皆对象)
小结
- 面向对象编程是一种编程思想,与技术无关
- 面向对象编程的本质是增加数据和功能的操作主体,即对象
- 面向对象中所有的数据和功能都是由主体(对象)来调用和操作
二、面向对象基础
思考:按照面向对象编程思想逻辑,先编写好对应的功能函数,然后再放到某个主体中。那么已经写好的函数如何放到某个主体中去呢?
引入:上述图片中描述的是思想的实现,在实际编写代码的过程中,肯定不是这么去实现的。面向对象中,对于一些思想产物有一些不同的称呼。
1.面向对象关键字说明【掌握】
定义:面向对象关键字说明,即在面向对象思想设计的程序中,有一些关键的结构用词。在面向对象中,关键结构包括以下几个:
- 类:class,是定义面向对象主体的最外层结构,用来包裹主体的数据和功能(函数)。类是一类具有共性事务的代表,代表的是事务的共性。
- 对象:object,是某类事务的具体代表,也是实际数据和功能操作的具体单元,也被称之为实例(instance)
- 实例化:new,从一个抽象的概念(类)得到一个符合抽象概念的具体实例(对象)的过程
- 类成员:member,指类class结构中的所有内容,类成员里有三种
- 方法:method,本质是在类class结构中创建的函数,也称之为
成员方法
或者成员函数 - 属性:property,本质是在类class结构中创建的变量,也称之为
成员变量
- 类常量:constant,本质是在类class结构中创建的常量
总结:面向对象基础中没有本质多出很多东西,基本只是将原来面向过程的内容进行一层包裹而已。
思考:面向对象编程到底是如何用代码实现这样的编程思想的呢?
引入:其实所有的编程都大同小异:编写程序–>运行程序。只是根据不同的思想会有不同的关键字让系统识别而已。
2.面向对象简单技术实现【掌握】
定义:面向对象思维的实现,就是利用面向对象提供的关键字和代码规范来进行编程,而这里面最核心的两个部分就是类class和对象object
- 定义类基本语法:class 类名{}
1 |
|
- 类class是一种结构,如果写好没有语法错误的情况下,代码不会执行(与函数定义一样),也无法打印输出
1 |
|
- 类的命名规范:类的命名规范与函数类似,区别在于人为的通常会将类名的首字母大写
1 |
|
- 如果碰到多单词组成的类名,通常使用驼峰法
1 |
|
- 类无法直接访问,需要得到类的具体对象才能访问,可以通过实例化new来实现对象的创建:new 类名[()]
1 |
|
- 对象创建后可以直接使用或者打印,但是为了方便后续使用,通常使用变量保存实例化的对象
1 |
|
总结
- 通过class关键字 + 类名 +{}创建类
- 类是一种结构,不会自动运行,也不能输出
- 通过new 类名实例化对象得到类的具体对象(具象)
- 可以通过new实例化无限个对象
思考:上面定义的类就是个空类,里面什么都没有,类里面能够放些什么东西呢?
引入:通常空类没有什么价值,需要往类里填充
类成员
,这样的类才有价值,这样的类实例化得到的对象才有意义。
3.类成员【掌握】
定义:类成员,指直接定义在类结构{}内部的一级成员,即直接依赖{}的成员。在PHP中类成员有三种:成员变量(属性),成员方法(成员函数)和类常量
- 成员变量:就是在类结构{}下直接定义的变量,但是定义的方式与普通变量稍微有点不同,需要在变量名字前使用一个关键字public,定义语法:public 变量名字 [= 值];,成员变量可以赋值也可以不赋值。
1 |
|
- 成员变量访问:成员变量必须通过对象才能进行访问,也就是需要先通过实例化得到对象,然后通过对象实现对成员变量的增删改查:访问语法:$对象名->属性名;
1 |
|
注意:删除属性和新增属性通常使用较少,更多的属性操作是访问和修改
- 成员方法:就是在类结构{}下定义的函数,函数内部的内容与原来函数结构一样,可以有分支、循环结构等
1 |
|
- 成员方法访问:成员方法也是需要通过对象进行访问的,访问语法为:$对象名->方法名字();
1 |
|
- 类常量:类常量是在类结构{}下定义的常量,类常量的定义只能使用一种方式:const 常量名 = 值;
1 |
|
注意:类常量不是由对象来进行访问,所以暂时不做访问讲解,到后面知识再讲。
- 类成员中:属性、类常量和方法都可以无限定义,但是定义的原则是相关性。除了以上三个类成员,不能在类结构{}中直接写其他任何代码
1 |
|
总结
- PHP类结构中有三种成员:属性、方法和类常量
- 类结构中只能有三种成员,不限数量,其他代码需要写到方法里面,否则报错
- 类中属性和方法的访问方式都是通过对象来调用:$对象->属性名/方法名();注意属性名不带$符号
- 类中定义属性不能直接使用属性名,需要使用符号public修饰
思考:属性本质是变量,只是放到类结构中而已,为什么一定要使用public呢?有什么用呢?
引入:在面向对象中,属性或者方法都是用来给对象访问的,但是有的时候并不是所有的内容都是允许对象在外部访问的,这个时候就出现
访问修饰限定符
来控制访问位置。
4.访问修饰限定符【掌握】
定义:访问修饰限定符,是一种用在属性或者方法前的修饰关键字,是用来控制属性或者方法的访问位置的。在PHP中访问修饰限定符分为三种:public、protected和private
- 首先需要明白一组概念类的内部和外部
- 类内部:是指类定义的内容内部,即类名后{}内部
- 类外部:是指类定义的外部内容,即类名后{}之外的所有地方
- 类成员的访问权限控制是内部访问(私有)、链内部访问(受保护)和全部访问(公有)
- public:公有,即表示所修饰的内容(属性或者方法)可以在当前类的内部访问,也可以在类的外部访问
1 |
|
- protected:受保护,即表示所修饰的内容只能在
类的内部
访问(在学习继承时再讲)
1 |
|
- private:私有,即表示所修饰的内容只能在
当前类的内部
访问
1 |
|
- 访问修饰限定符不只是限定属性,也用来限定方法
1 |
|
总结
- 访问修饰限定符分为三种:public、protected和private,访问权限依次降低
- 访问修饰限定符限定的是成员到底在哪里能被访问,私有和受保护都只能在类内部访问,公有可以在任何地方访问(但都必须是对象去访问)
- 属性必须写清楚访问修饰限定符,方法可以省去(不建议),因为系统默认是public
思考:前面访问修饰限定符描述的只是在外部调用访问,怎么能够体现在类的内部访问呢?
引入:类的内部如果是在类结构{}后,除了类三种成员,不能有别的代码;但是类成员方法内部可以写任何代码,因此可以在类方法内部实现访问:此时方法运行的内部环境属于类内部。
5.类内部对象【掌握】
定义:类成员属性和方法的访问必须通过对象才能访问,类外部可以通过实例化得到类对象从而实现成员访问,但是类内部又不能访问类外部的对象(未知),此时类的内部就内置了一个对象$this代表来访对象。
- 尝试在类内部方法中访问属性
1 |
|
注意:方法本质是定义在类内部的函数,因此受制于作用域的问题,在方法内部访问的变量系统认定为局部变量(必须内部定义或者参数传入),否则就会提示未定义
- 类内部访问类成员,需要通过对象来进行访问
1 |
|
- 思考这样一个问题:类一开始定义好,但是对象实例化是在需要使用该类的时候来调用的,如何保证外部保存对象的变量一定是$s呢?对象可以创建多个,如果有多个对象变量,那内部怎么实现呢?如果系统内部能够检测到是哪个对象在调用方法,并且能够复制对应对象就好了,这个就是类成员方法
内置对象$this
1 |
|
由上述代码可见:$this代表的是对象,而$this所在环境为类内部的方法内部,所以$this对象是在类内部访问,因此可以访问所有的属性和方法,不受访问修饰限定符限制
$this、class和new之间的关系原理
class是定义类结构,属于非执行段代码,因此会被加载到代码段(编译阶段)
new是实例化对象,先判定类在内存(代码段)是否存在
- 类不存在,报错;
- 类存在,将类内部的属性部分复制一份,然后在内存(堆区)开辟一块内存空间,将属性放到里面,同时内部有一个指针指向类的内存空间(代码段)
- 对象访问属性即访问的是对象空间里存储的部分
- 对象访问方法是对象通过内部指针找到类空间中的方法,然后在内存(栈区)开辟运行
$this是系统在方法内置的对象通用名字
对象在调用方法的时候,系统会自动找到对象所保存的内存地址(堆区),然后把地址赋值给$this
方法内部的$this就代表调用当前$this所在方法的外部对象
$this的本质是函数内部的一个局部变量,只是系统自动对其进行赋值,而且一定是调用方法的对象本身
总结
- 类内部方法内有一个内置对象$this,代表访问该方法的外部对象
- 类在实例化对象的时候要保证内存中有该类
- 一个类可以实例化多个对象,每个对象访问成员方法时,$this就代表对应对象
思考:面向对象中,属性在定义类结构的时候是否需要赋值?到底该如何使用访问修饰限定符呢?
引入:站在开发者的角度,并没有任何强制规定属性在类中定义的时候就需要初始化,也没有任何规定访问修饰限定符到底该用哪个。但是从业务需求和隐私保护角度出发,有一些规则需要我们遵循。
6.面向对象开发规范【掌握】
定义:面向对象规范是指我们在开发时,在技术没有强制规定的情况下,应该采用什么样的方式来协助我们进行代码的书写。
- 属性的初始化:属性是类对于同类事务所抽离出来的共性
数据
,本身在类结构中没有价值,是当具体对象产生之后,属于对象本身的。因此在进行类中定义属性的时候,通常不会对属性进行初始化,除非属性本身的值也具有共性
1 |
|
- 属性的初始化:属性在类中初始化的基本判定就是数据是否初始化的时候统一,如果数据统一(即初始化),那么所有对象在生成之后,就拥有对应的值;如果不初始化,意味着所有对象在生成后,应该对属性进行赋值。
1 |
|
- 访问修饰限定符选择:访问修饰限定符是用来限制类成员被对象访问时对象所处位置的。访问的权限从public、protected到private依次变小。使用规则如下:
- 设定好的类成员本身不会被外部用到,那么应该使用private或者protected
- 设定好的类成员一定会给外部访问,使用public
- 属性通常private居多,如果需要外部访问属性,通常会定义相关方法来实现属性的查看和修改,因为可以在方法内对数据逻辑进行代码控制,安全
- 总之一句话:尽可能增加类对成员的控制(尽可能多使用private,少使用public)
1 |
|
总结
- 属性在类中定义的时候,通常不会初始化值,除非所有类实例化得到的对象的某个属性需要是统一值
- 应该尽可能增加类对成员的控制,即使用范围较小的访问修饰限定符优先
- 属性通常是私有化的,一般是通过设定方法来实现属性的访问和修改
思考:属性一般不会在类定义的时候就初始化,那实例化对象的时候,属性的数据不都是为空吗?这样的数据有什么价值呢?
引入:属性属于对象使用,在类中初始化的确没有太多价值,但是对象实例化之后如果属性数据为空也没有价值。因此通常属性都是在对象实例化的时候进行初始化的,这个时候就需要用到一种自动
构造
的方式来完成
7.构造方法【掌握】
定义:构造方法__construct(),是一种类结构特有的特殊方法,该方法由系统规定好,开发人员在定义的时候只需要写抄一遍,有了构造方法的类在实例化对象之后,对象就会自动调用。
- 构造方法实现:在类中增加一个方法__construct()即可
1 |
|
- 构造方法也是一个普通方法,不普通的地方在于,类实例化得到的对象会马上自动调用
1 |
|
- 构造方法的意义:构造方法是对象实例化的时候用来初始化对象的资源的,所以通常是用来初始化对象的属性或者其他资源初始化
1 |
|
- 如果属性的数据在构造方法中初始化是固定写死的,那么与直接在定义类的时候初始化属性一样。意味着数据没有任何价值(所有对象都相同),因此通常是通过构造方法的参数来实现数据的外部传入
1 |
|
- 一旦构造方法拥有了形参,那么对象在调用该方法的时候就需要传入对应的实参,而构造方法又是自动调用的,所以需要在实例化对象的时候使用new 类名(构造方法对应的实参列表)来实现
1 |
|
注意:之前所说的new 类名
和new 类名()
没有区别是因为没有构造方法,或者构造方法没有参数限定,一旦构造方法有了参数,那么new 类名
就不能直接使用了。
- 构造方法不管再怎么特殊,也是用户定义的方法,言外之意除了在实例化对象时对象会自动调用之外,我们也可以手动调用构造方法(但是一般没有价值,因为对象实例化时会自动调用)
1 |
|
总结
- 构造方法__construct()是一种系统内置的方法,该方法的特性是会在对象实例化之后,对象立即自动调用
- 构造方法的目的就是为了初始化资源,包含对象属性和其他资源
- 一旦构造方法定义好之后,且构造方法自带参数,那么就只能使用
new 类名(参数列表)
方式才能正确实例化 - 构造方法可以当做普通方法由对象调用(不建议)
思考:构造方法是用来实现资源的初始化的,那么有没有需求来实现资源的释放呢?
引入:理论上讲,一种资源被初始化通常需要在不用的时候给释放掉,这样的程序才是好的程序。而PHP有个特点,就是脚本运行结束后系统自动回收所有内存,这就决定了PHP开发者大多数时候不喜欢额外做释放资源的工作。同样的,对象有构造方法初始化资源,也有
析构方法
释放资源。
8.析构方法【了解】
定义:析构方法__destruct(),也是一种类结构中特殊的方法,与构造方法一样,也是系统规定好,只需要开发人员抄一遍即可,对象在被销毁时会自动调用。
- 析构方法实现:类中增加一个__destruct()方法
1 |
|
- 析构方法调用:析构方法是在对象被销毁时自动,对象的“垂死挣扎”
1 |
|
- 析构方法是对象用来调用释放对象中的资源,不是用来删除对象的
1 |
|
- 析构方法也是普通方法,可以由对象直接调用
1 |
|
- 对象销毁情形
- 保存对象的变量被用来保存其他数据,导致对象内存没有任何变量引用
- 删除对象变量
- 脚本执行结束:释放所有变量
总结
- 析构方法是一种对象销毁时自动调用的方法
- 析构方法是用来对象销毁自身所占用的资源
- PHP中脚本执行结束,系统会自动回收所有资源,因此一般PHP中很少使用析构方法
思考:在PHP中,变量赋值是值传递的,即变量赋值给另外一个变量,两个变量是不同的。如果变量保存的是对象,那么给变量赋值的时候,会产生两个对象吗?
引入:对象的产生是由实例化得到的,因此如果是将保存对象的变量赋值给另外一个变量,这个过程是不会产生新的变量的。
9.对象传值【了解】
定义:对象传值,其实就是将保存对象的变量赋值给另外一个变量,在PHP中,对象的传值是引用传递的:即一个对象变量赋值给另外一个变量,两个变量指向同一个对象的内存地址,即只有一个对象。
- 对象传值就是保存对象的变量赋值给另外一个变量
1 |
|
- 对象传值是引用传递,不管对象赋值给多少个变量,内存中只有一个对象
1 |
|
总结:对象传值是引用传值,一般情况下不会在项目中用到。
思考:前面类成员中,属性和方法都已经清楚的知道是由对象来访问,唯独类常量没有被访问,类常量到底是如何访问的呢?
引入:类常量通过名字分析就知道是属于类的,因此类常量的访问是通过类来进行访问的。
10.范围解析操作符(类常量访问)【掌握】
定义:范围解析操作符,由两个冒号组成“**::**”,是专门用于类实现类成员操作的,可以实现类直接访问类成员。
- 类常量的普通访问尝试:尝试使用对象进行访问
1 |
|
- 以上案例可以看出,对象无法访问类常量,那是因为类常量的定义本身就是用来给类访问的,对象是用来访问属性和方法的,类常量的访问方式为:类名::常量名
1 |
|
- 分析:类常量是固定的,而对象的属性是不同对象而不同的,成员方法简单的理解也是为属性本身进行加工的。因此有一些东西是专属于类的,而有部分内容是专门为对象提供的,所以就会有不同的成员拥有不同的访问方式
总结
- 类访问成员的方式是使用范围解析操作符“**::**”访问,由类名直接访问:类名::类常量
- 类本身是通过对同类对象的抽象而形成,所以属性和方法本身都是由对象来访问
- 类也需要有一些自身的数据和操作,这些就由类来进行访问
思考:类要访问的成员,都只能是类常量吗?毕竟类常量是一旦定义不可修改的,这样没有多大价值。
引入:在类中,类成员只有三种:类常量、属性和方法,为了区分对象和类访问,可以使用
static
关键字来修饰属性和方法。
11.静态成员【掌握】
定义:静态成员,使用static关键字修饰的类成员,表示该成员属于类访问。PHP静态成员有两种,静态属性和静态方法。
- 静态属性:在类中定义属性的时候使用static关键字修饰,访问的时候只能使用类+范围解析操作符+静态属性访问
1 |
|
- 静态方法:在定义方法的时候使用static关键字修饰,访问的时候使用类+范围解析操作符+静态方法名字()访问
1 |
|
- 在类的内部也可以访问静态成员,同样是使用类名+范围解析操作符+静态属性/静态方法()
1 |
|
- 静态方法本质也是类中定义的方法,因此也可以使用对象进行访问,但是不建议
1 |
|
- 同理,方法也是在类内部,在编译时就存在,因此可以通过类来进行访问,使用范围解析操作符,但是非常不建议(会报错:因为类只允许访问静态成员和类常量)
1 |
|
- 静态方法本质是给类访问,所以不允许在静态方法内部使用$this对象
1 |
|
总结
- 为了保障类能直接访问数据和操作数据,可以在属性和方法前增加static关键字变成静态属性和静态方法
- 类通过类名+范围解析操作符+静态成员的方式进行访问
- 静态成员也收访问修饰限定符的限定,访问权限与普通属性和方法的限制一样
- 对象可以无条件访问静态方法,而类只能访问不带$this的普通方法(不建议)
- 静态成员是给类访问的,非静态成员是给对象访问的
- 静态成员的访问效率比非静态成员高,因此有种说法是能用静态的时候就不用非静态
思考:类的内部的静态方法内可能会访问到其他静态成员或者类常量,这个时候必须每次使用类名,那如果类名修改的话岂不是很麻烦?
引入:静态成员或者类常量的访问是必须使用类名进行访问的,不过为了方便类名的更新,减少开发者维护成本,系统引入了一个类似$this代表对象的方案:self关键字
12. self关键字【掌握】
定义:self关键字是一种在类的内部(方法里面)使用,代替类名的写法。能够保障用户方便修改类名字。
- self是用来代替类名的,与范围解析操作符
::
一起使用的
1 |
|
- self也可以在类的内部方便实例化对象:比如构造方法被私有化之后,就没有办法在类外部实例化对象,此时可以在类内部进行对象实例化
1 |
|
总结
- self是一种在类内部用来代替类名的关键字
- self可以用来在类内部访问静态成员(类常量也可以)
- self也可以在类内部用来实例化对象(代替类名:new self())
思考:类不管是直接访问静态成员还是先实例化然后访问类成员,都有一个前提:类必须在内存中存在,那是不是意味着所有的类操作之前,都必须先确认类在内存中是否存在呢?
引入:类的任何操作都需要确认类在内存存在的,不过类文件通常是一个类单独形成一个文件的。所以在操作之前需要进行类的加载。
13.类的加载【重要】
定义:所谓类的加载,本质是因为类的访问必须保证类在内存中已经存在,所以需要在用类之前将类所在的PHP文件加载到内存。
- 手动加载:即要访问某个类之前,使用文件包含将类所在的文件加载进来
1 | 类文件:Saler.php |
- 加载类文件是一种比较消耗资源的方式,所以有的时候不确定类是否在内存中存在,可以事先使用class_exists()函数来判定是否存在,存在就不用加载,不存在才加载
1 |
|
- 自动加载:PHP没有那么智能的系统自动加载,所谓自动加载只是PHP提供了一种
加载机制
:即实现定义一个函数__autoload(),然后当系统需要使用类,而内存中又不存在的时候,系统就会自动调用__autoload()函数来加载类文件.
1 |
|
- 一个系统里,可能类文件会放到不同的路径下,因此一个完整的自动加载函数,应该要进行文件判定以及加载功能
1 |
|
注意:自动加载是指按照开发者规定的路径去寻找对应的文件,并实现包含。如果文件不存在,那么系统会在使用类的时候报错,因为这是开发者自己犯的错,系统不能规避。
- 随着PHP版本的提升,在7以后,不怎么建议直接使用__autoload()函数,而是采用一种注册机制,将用户自定义的函数,放到系统内部,使用spl_autoload_register(定义好的函数)。本质与__autoload()一样
1 |
|
注意:该方式其实本质就是通过两步完成了__autoload()一步的操作,但是spl_autoload_register()函数可以注册多个自定义的加载函数,更方便管理。
1 |
|
1 |
|
总结
- 类的使用必须先保证内存中该类存在
- 可以使用手动加载来确保类的使用安全:优点是明确,缺点是繁琐(类文件名字可以随意没有规范)
- 可以使用自动加载来让系统按照开发者设定的路径和方式去寻找类,并尝试加载到内存(尽量让类文件名字统一,保证类名和文件名有关联)
- 自动加载可以使用__autoload()函数来实现,也可以使用自定义函数+spl_autoload_register()注册共同实现(后者推荐)
- 基本上所有的框架都在使用自动加载机制
思考:对象只能通过实例化产生,而且即使是将对象赋值给变量,得到的还是同一个对象。如果有的时候需要得到新的对象,除了实例化之外还有其他方法吗?
引入:对象理论上应该通过实例化来产生,这是对象产生的根本。但是有时候如果想在已有对象上去产生一个新的对象,可以通过
克隆
来实现。
14.对象克隆【了解】
定义:克隆对象clone,即通过已有的对象复制一个新的同样的对象,但是两者之间并非同一个对象。
- 对象克隆是通过clone关键字实现,即:clone 对象;
1 |
|
- 克隆出来的对象与原来对象是两个内存地址,因此是两个不同的对象
1 |
|
- 对象在实例化的时候会自动调用存在的构造方法__construct(),同样的,在类的内部,PHP允许定义一个__clone()的方法,在对象被克隆后,新克隆出来的对象会自动调用
1 |
|
- 如果不允许对象被克隆,可以将__clone()方法私有化(本质是不允许对象在外部被克隆)
1 |
|
总结
- 对象可以通过克隆来得到新的对象(以前只有实例化)
- 克隆出来的对象会自动调用类中对应的__clone()方法(如果有)
- 可以通过私有化克隆方法来实现禁止外部对象克隆
思考:现在掌握了很多面向对象相关的内容,但是什么时候封装类?怎么封装类?如何使用类成员?
引入:其实类的封装并没有多么复杂,但是需要一个熟练的过程来确定哪些东西可以放到类里,该用什么样的形式等。我们通过封装一个数据库的操作类来综合练习一下。
15.封装数据库操作类【掌握】
定义:封装数据库操作类,即根据数据库的操作需求,来确认数据库操作类该有什么样的功能,以及这些功能该如何实现。
- 一个类通常就是一个文件,所以要先确定文件的名字:通常类文件命名规范有两种
- 文件名字与类名字一样,如Sql.php
- 为了区分普通PHP文件,增加中间类描述,如Sql.class.php
- 现在PHP几乎都是面向对象编程,所以通常采用第一种方式:因此当前命名数据类的文件为:Sql.php
- 确定类文件名字后其实也就确定了类名字,因此可以创建一个Sql类
1 |
|
- 类的创建分两种:一是特定使用,即类里面的所有内容只为某次使用;二是通用,即工具类,以后很多地方可以用。
- 特定使用,功能可以不用太灵活
- 通用工具,功能应该大众化,数据的变化会比较多
数据库类以后凡是要操作数据库的地方都可以用得到,很多项目都会用到,所以应该是个通用工具类,因此要考虑其到处可用的特性,让其能够灵活
- 数据库的操作最基本的特性不会改变:即需要连接认证,而连接认证的信息是灵活的,所以可以通过设定属性来控制,这些信息也都是不同使用者不同的,应该可以改变,所以可以通过构造方法来实现数据传入
1 |
|
注意:方法设定的原则是一个方法只实现一个简单的功能,不要多个功能堆积到一个方法中。
- 数据库属性会在实例化Sql对象的时候自动初始化
1 |
|
- 数据库要操作的第一件事就是连接认证,所以需要一个连接认证的功能。这里可以使用mysqli面向对象的方法。但是需要建立一个方法来实现连接认证:连接是否成功?
1 | //在上述类中增加一个方法:实现连接认证功能 |
- 用户调用Sql类的目的一定是为了操作数据库,那么用户在实例化之后就需要调用连接认证的方法。为了方便用户操作,可以帮助用户省去调用这一步骤:在构造方法中调用该方法
1 | //在上述构造方法中调用连接认证方法 |
- 至此,一旦实例化Sql类对象,就可以实现数据库的连接,但是此时还存在一个细节问题:字符集,为了保证数据库连接的正常操作,需要新增一个方法设定字符集
1 | //在Sql类中增加设定字符集的方法 |
- 由于sql_connect方法中得到的连接对象mysqli的$link是一个局部变量,无法在其他方法里使用,因此需要将连接对象$link提升为整个类的内部可用:提升为属性
1 | //在Sql类中增加属性,保存对象 |
- 继续完成设定字符集的功能:使用属性保存的mysqli连接对象
1 | //在Sql类中完善设定字符集的方法 |
- 设置字符集的SQL指令虽然简单,但是因为有数据来源于外部,所以存在出错的风险,因此需要进行SQL错误判定
1 | //在Sql类中完善设定字符集的方法 |
- 同样的,字符集的设置也是为了操作数据库的前置条件,属于初始化的一部分。因此需要字符集设置的方法在构造方法中调用,方便用户操作
1 | //在Sql类的构造方法中调用字符集设置方法 |
- 至此:数据库的初始化操作已经完成,此时要考虑的事情是用户调用数据库类是为了干什么?为了执行SQL指令,也就是增删改查。在mysqli中所有的SQL执行都是通过mysqli::query()方法执行,但是我们可以根据需求封装两个函数:写方法和查方法(包含一条和多条查询)
1 | //在Sql类中增加一个写方法:SQL指令来自于调用处提供 |
- 上述已经完成了数据库类要实现的基本功能:实现SQL指令的执行和结果返回,但是从功能细节的角度出发还需要进行完善:插入操作后要获取自增长id,更新和删除操作受影响的行数,查询操作中记录数量。这种使用可以通过设置方法来实现获取(自增长id),也可以通过增加属性来实现(属性简单)
增加属性:受影响的行数,自增长id,查询记录数
1 | //在Sql类中追加属性 |
在写操作sql_exec中,为受影响行数赋值
1 | //修改sql_exec方法 |
在读操作中,为记录数
1 | //修改sql_query方法 |
增加一个方法专门获取上次插入数据的自增长ID(因为这个是人为区分,不方便放到sql_exec中)
1 | //在Sql类中增加一个方法获取上一次自增长操作id |
- 至此:数据库类的功能已经实现,接下来要考虑类的定义规范:类对成员的控制性
- 属性如果不需要给外部访问,私有
- 方法如果只是内部调用,私有
- 利用数据库类实现数据库的写操作和读操作
总结
- 类的封装是以功能驱动为前提,相关操作存放到一个类中
- 一个类通常是一个独立的文件,文件名与类名相同(方便后期维护和自动加载)
- 类中如果有数据需要管理,设定属性(固定数据可以使用类常量)
- 类中如果有功能需要实现(数据加工),设定方法
- 一个功能通常使用一个方法实现,方法的颗粒度应该尽可能小(方便复用)
- 应该尽可能增加类对成员的控制:即能私有尽可能私有
- Post title:PHP面向对象基础
- Post author:John_Frod
- Create time:2021-01-25 21:38:12
- Post link:https://keep.xpoet.cn/2021/01/25/PHP面向对象基础/
- Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.