面向对象基础

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

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

概念

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

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

    • 优点:执行效率高

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

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

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

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

面向过程编程

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

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

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

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

小结

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

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

一、面向对象编程思想

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

概念

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

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

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

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

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

原理

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

面向过程编程思想原理

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

面向对象编程思想原理

小结

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

二、面向对象基础

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

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

示例

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
# 确定是消费者购买(类)
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,类成员)
1
2
3
class 类名{

}
  • 实例化:类产生对象的过程
1
2
new 类名;
new 类名(); # 使用较多
  • 对象:根据类产生的某个具体存在的实体(instance),对象一般使用变量保存
1
$object = new 类名();

步骤

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

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

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

示例

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

1
2
3
# 定义一个空类
class Nothing{
}

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

1
2
3
4
5
6
7
8
9
10
11
12
# 实例化,并将产生的对象保存在变量中
$n = new Nothing();
# 打印对象
var_dump($n);

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

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

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

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

1
2
3
4
5
6
7
8
# 有效类名
class My1{}
class My_1{}
class _My1{}

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

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

1
class MyClass{}

小结

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

3、类成员

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

概念

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

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

步骤

1、声明类结构

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

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

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

5、对象实例化

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

示例

1、声明类结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 定义买家类:买家有姓名,有钱
class Buyer{
# 属性声明
$name; # 错误,类内部属性必须使用访问修饰限定符
public $name; # 正确:没有赋值
public $money = 0; # 正确:有赋值

# 方法声明
function display(){
echo __CLASS__; # 魔术常量,输出类名
}

# 类常量声明
const BIG_NAME = 'BUYER';
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
# 实例化对象
$b = new Buyer();
# 访问属性
echo $b->money;
# 修改属性
$b->money = 1000;
# 删除属性
unset($b->name);
# 新增属性
$b->age = 20;

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

注意

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

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

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

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

1
2
3
4
5
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、公有成员访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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、受保护和私有成员设定(当前受保护和私有一致,需要高阶知识才会有区别)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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、尝试在类内部方法中访问属性

1
2
3
4
5
6
7
8
9
10
11
12
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、类内部访问类成员,需要通过对象来进行访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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的本质是函数内部的一个局部变量,只是系统自动对其进行赋值,而且一定是调用方法的对象本身
  • 面向对象编程代码运行内存关系

面向对象编程代码运行内存关系

小结

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

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

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

6、面向对象开发规范

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

概念

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

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

示例

1、属性初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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、访问修饰限定符选择

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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()即可

1
2
3
4
5
6
class Saler{
# 构造方法
public function __construct(){
echo __CLASS__;
}
}

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

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

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

1
2
3
4
5
6
7
8
9
10
11
class Saler{
# 属性
public $count;
private $money;

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

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

1
2
3
4
5
6
7
8
9
10
11
class Saler{
# 属性
public $count;
private $money;

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

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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()方法

1
2
3
4
5
6
class Saler{
# 析构方法
public function __destruct(){
echo __FUNCTION__;
}
}

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

1
2
3
4
5
6
7
8
9
10
# 实例化对象
$s = new Saler();

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

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

# 脚本执行结束自动释放

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

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

小结

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

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

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

9、对象传值

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

概念

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

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

步骤

1、定义类结构

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

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

示例

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

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

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

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

小结

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

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

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

概念

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

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

步骤

1、定义类结构

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

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

示例

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

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

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

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

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

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

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

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

小结

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

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

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

11、静态成员

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

概念

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

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

步骤

1、定义类结构

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

3、使用static关键字修饰

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

示例

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

1
2
3
4
5
6
7
8
class Saler{
# 属性
public $money = 0;
public static $count = 0; # 静态属性
}

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

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

1
2
3
4
5
6
7
8
9
class Saler{
# 方法
public static function showClass(){
echo __CLASS__;
}
}

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

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

1
2
3
4
5
6
7
8
9
10
11
class Saler{
# 属性
private static $count = 0; # 私有,不允许外部直接访问
# 方法
public static function showClass(){
echo Saler::$count;
}
}

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

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

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

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

1
2
3
4
5
6
7
8
class Saler{
public function testStatic(){
echo __FUNCTION__;
}
}

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

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

1
2
3
4
5
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是用来代替类名的,与范围解析操作符::一起使用的

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

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

1
2
3
4
5
6
7
8
9
10
11
12
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():系统自动调用,需要传入类名,在函数内部实现类的手动加载
    1
    2
    3
    function __autoload($classname){
    # 找到对应的文件路径和命名规范,手动加载
    }
    • 自定义函数:自己定义类的加载实现,然后通过spl_autoload_register注册到自动加载机制(可以注册多个自动加载)
    1
    2
    3
    4
    5
    6
    7
    # 自定义类加载函数
    function 自定义函数($classname){
    # 找到对应的文件路径和命名规范,手动加载
    }

    # 注册自动加载
    spl_autoload_register('自定义函数名字');
  • 自动加载要求在声明类的时候有良好的规范

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

步骤

1、定义类文件

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

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

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

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

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

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

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

示例

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

1
2
3
4
5
6
7
8
9
# 类文件:Saler.php
<?php
class Saler{}
?>

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

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

1
2
3
4
5
6
7
8
# 使用Saler类,但是不确定内存中是否存在
if(!class_exists('Saler')){
# 不存在:加载
include_once 'Saler.php';
}

# 使用
$s = new Saler();

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 定义自动加载
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()一样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 定义一个函数,用来加载类文件
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()函数可以注册多个自定义的加载函数,更方便管理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 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 对象;

1
2
3
4
5
6
7
8
9
10
11
class Saler{
# 属性
public $count;
private $money;
}
# 实例化
$s1 = new Saler();
$s1->count = 1;

# 克隆
$s2 = clone $s1;

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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()方法私有化(本质是不允许对象在外部被克隆)

1
2
3
4
5
6
7
8
9
10
11
12
13
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类

1
2
# 数据库操作类
class Sql{}

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 数据库操作类
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对象的时候自动初始化

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 对外提供属性,记录错误数据(外部如何处理,不需要我们管)
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、连接认证包括数据库选择设定好后,此时还存在一个细节问题:字符集,为了保证数据库连接的正常操作,需要新增一个方法设定字符集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 在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并检查错误的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 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、上述功能本质也可以是一个写操作(不完整),但是写操作是有业务性的:返回受影响的行数,因此独立增加一个写操作方法,调用上述方法实现,并根据结果返回受影响的行数

1
2
3
4
5
6
7
8
# 写操作
public function write($sql){
# 调用方法检查执行
$res = $this->check($sql);

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 属性:记录查询结果中的列数
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、读取操作:读取多条记录:可能需要知道总的记录数以及查询结果的列数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 属性:记录查询结果的行数
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类就可以直接进行相应的业务处理:所以可以将连接认证、字符集设置在初始化方法中实现(构造方法)

1
2
3
4
5
6
7
8
9
10
11
12
13
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、确定类的控制:不需要外部访问的私有,需要外部访问的公有,重要的数据私有并增加公有操作方法进行安全控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 数据库初始化资源私有:不需要外部访问
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、类中需要实现的功能应该由具体的业务来实现支撑

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

设计模式

定义:设计模式(Design pattern) 是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。 设计模式有很多,在PHP中通常只用两种设计模式

  • 单例模式
  • 工厂模式

思考:有这么一种需求,希望一个类有且仅有一个对象,这样可以保证对象的完整性,请问该如何实现呢?

引入:我们将这种一个类最多只有一个对象称之为单例,在操作系统中这种方式需求很多,如资源管理器。而通过代码实现这种单例的设计思路,我们称之为单例模式

1. 单例模式【掌握】

定义:单例模式singleton,是一种类的设计只会最多产生一个对象的设计思想。

  1. 首先定义一个空类,叫做Singleton
1
2
3
4
5
6
<?php
//创建一个空类
class Singleton{

}
?>
  1. 思考:对象的产生是通过实例化产生的,而实例化是一种不可控行为,即可以产生无限多个对象,所以应该禁止:即”禁止实例化“,之所以是引号,是因为只能禁止在类外部实例化对象,私有化构造方法
1
2
3
4
5
6
//在上述类中增加私有化构造方法
class Singleton{
private function __construct(){}
}
//尝试外部实例化
$s = new Singleton(); //致命错误:不能访问私有方法
  1. 思考:一旦外部不能实例化对象了,那就意味着根本“不可能”产生对象了,此时就只能想办法在还没有产生对象的时候就进入到“类内部”,意味着只能通过静态方法让类直接进入到类的内部
1
2
3
4
//在上述类中增加公有静态方法
public static function getInstance(){

}
  1. 思考:进入类的内部依然还是没有对象,此时需要在静态方法内部进行对象实例化,并且把得到的对象返回到外部
1
2
3
4
5
6
7
//修改公有静态方法:获取对象,并返回给外部调用出
public static function getInstance(){
return new self();
}

//外部获取对象
$s = Singleton::getInstance();
  1. 思考:此方法开启了实例化对象的窗口,但是此时新问题出现:无限调用静态方法依然可以得到多个对象。如果想要该方法只返回一个对象,就得保证类内部有办法存着某个产生的对象,第一次产生新的,后面返回旧的。此时需要使用静态属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//增加静态属性:私有,不允许外部修改,否则外部修改之后就没有意义了
private static $object = NULL; //初始化为NULL,没有对象

//修改静态方法
public static function getInstance(){
//判断内部属性是否存在对象(is_object函数):最好的判定是存的对象是当前类的 instanceof
if(!(self::$object instanceof self)){
//当前保存的内容不是当前类的对象
self::$object = new self();
}

//返回对象给外部
return self::$object;
}
  1. 此时可以保证外部无论多少次调用公有静态方法获取实例,都会只得到一个对象。但是此时外部对象依然可以产生新的对象:因为克隆,所以还必须禁止对象的克隆,即在类内部私有化克隆方法
1
2
//在Singleton类中增加私有化的__clone()方法
private function __clone(){}

总结

  1. 单例模式就是设计的类最多只能得到一个对象
  2. 单例模式的设计规范就是“三私一公”
  • 私有化构造方法:禁止在类外无限实例化对象
  • 私有化克隆方法:禁止对象无限克隆对象
  • 私有化静态属性:保存类内部实例化得到的对象
  • 公有化静态方法:允许外部通过调用类内部方法获取对象
  1. 单例模式如果还有其他功能诉求,可以在类中增加相应的其他类成员
  2. 单例模式的目的是为了保护资源的唯一性

思考:一个类可能在多个地方需要实例化,如数据库类,每次操作数据库都要实例化,如果类名改了,那是不是有很多地方代码要改?

引入:因为类在实例化对象的时候是new 类名实现,所以如果出现类名的修改,那么所有的new操作的确都需要重新修改代码。如果有一种方式,可以在某个地方实现所有的实例化,那么修改就会变得简单,这个就需要用到工厂模式

2. 工厂模式【掌握】

定义:工厂模式factory,是指像工厂一样流水线生产对象,由一个地方生产对象,其他位置就不需要额外实例化对象,从而可以方便后期代码统一的维护。而且工厂模式下可以方便隐藏真实的类结构,因此也更加安全。

  1. 工厂模式针对的是“相同模型”的统一产出,即使用工厂模式产出对象对应的类都有相同的结构或者功能。所以,首先要有一批具有类似功能的类(其实本质是同样的大类下的小类)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//三个类文件
//人是一种大类,人下应该有男人、女人、人妖,各自成类
class Man{
public function display(){
echo '这是男人<br/>';
}
}

class Woman{
public function display(){
echo '这是女人<br/>';
}
}

class Ladyboy{
public function display(){
echo '这是人妖<br/>';
}
}
  1. 以前访问这些类的话都需要通过new 类名来实现,多出使用就多次new。如果使用工厂模式的话,就需要增加一个工厂类:HumanFactory
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
//人类工厂
class HumanFactory{
public function getInstance($classname){
return new $classname(); //可变变量使用
}
}

$hf = new HumanFactory();
$m = $hf->getInstance('Man');
$m->display(); //这是男人

$w = $hf->getInstance('Woman');
$w->display(); //这是女人
  1. 上述工厂类在对象生产的时候,额外产生了一个工厂类的对象,该对象无实际含义,因此可以使用更优的方式来生产对象:静态工厂
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
//静态人类工厂
class HumanFactory{
public static function getInstance($classname){
return new $classname(); //可变变量使用
}
}

$m = HumanFactory::getInstance('Man');
$m->display(); //这是男人

$w = HumanFactory::getInstance('Woman');
$w->display(); //这是女人
  1. 以上模式虽然也是工厂生产对象,但是是建立在使用者知道类名的前提下,而且如果原类名修改,依然需要多处修改代码,所以没有达到工厂模式的真实目的。修改工厂模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
//静态人类工厂
class HumanFactory{
public static function getInstance($flag){ //flag只是一种标志:不是类名
//$flag可以是m代表Man,w代表Woman,L代表Ladyboy
switch($flag){
case 'm':
return new Man();
case 'w':
return new Woman();
case 'L':
return new Ladyboy();
default:
return null; //匹配失败,返回空
}
}
}

$m = HumanFactory::getInstance('m');
$m->display(); //这是男人

$w = HumanFactory::getInstance('w');
$w->display(); //这是女人

总结

  1. 工厂模式是一种按需生产对象的模式
  2. 工厂模式通常是需要在大型项目中,会出现很多的相同功能的类,此时可以使用工厂产生对象
  3. 工厂模式的优点是能够方便后期对类的维护(更名)
  4. 工厂模式的缺点是随着功能增加,会需要增加很多开发量(开发多个工厂)

面向对象高级

思考:面向对象也就是把函数和变量常量之类的多用了一层类包裹,然后多了个对象访问而已嘛,似乎也不像面向对象思想说的那样好啊?

引入:类和对象是面向对象的基础部分,是将基本思想使用代码方式设计简单实现。面向对象还有很多高级的部分,更能够体现面向对象思想编程的强大。

  • 面向对象三大特性
  • PHP继承
  • 抽象类
  • 接口
  • 对象遍历

总结:面向对象核心的思想在于符合人类的思维逻辑,同时能够方便后期维护开发,实现代码的重复利用。这些都将在高级里面体现。


一、 面向对象三大特性

引入:在面向对象思想中,有三大特性:封装继承多态

思考:我们在使用类和对象来实现编程的时候,到底是遵循着什么样的逻辑呢?为什么要这样去规范类呢?

引入:面向对象很重要的思想就是隐藏,即外部不知道类内部是如何实现业务逻辑的,只管调用和使用结果,这个就是面向对象的三大特性之一:封装

1. 封装【掌握】

定义:封装,字面意思就是将内容装到某个容器中,并进行密封保存。在面向对象思想中,封装指将数据和对数据的操作捆绑到一起,形成对外界的隐蔽,同时对外提供可以操作的接口

  1. 数据:即要操作的数据,在面向对象的具体体现就是类成员属性(属性和静态属性)和类常量,这些都是在类的内部定义的用来保存数据的
  2. 数据的操作:即数据加工过程,在面向对象的具体体现就是方法,内部定义业务逻辑对数据进行加工处理。
  3. 捆绑到一起:即使用类结构{}将属性、类常量和方法存放到一起,成为一个整体
  4. 对外提供可操作的接口:即提供可以供外部访问的类成员(通常是方法)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//封装特性
class Saler{ //捆绑内容到一起,形成对外界隐蔽的整体
const ALLOW = true;
private static $count = 0;
private $money = 0; //数据:属性和类常量保存

public function getMoney(){ //数据操作
return $this->money;
}
}

//外部
$s = new Saler();
$s->getMoney(); //外部只能访问类中公有的方法,具体实现不可见

总结

  1. 封装是从对象抽象形成类的过程
  2. 封装是一种将数据和数据操作捆绑到一起,形成对外部的隐蔽,同时提供对外操作的接口
  3. 封装是面向对象第一大特性,也是面试官喜欢询问对面向对象思想理解的知识

思考:面向对象思想是符合人类的思维逻辑的,那现实中的“富二代”之类的能够在面向对象思维里体现吗?

引入:面向对象最核心的一部分就是代码的重复利用的问题,而“富二代”的本质就是前人栽树后人乘凉的问题,所以面向对象的第二大特性就是继承

2. 继承【掌握】

定义:继承,即有些类似的对象中,如人是一种大类,下面可以细分出一些小类,如男人、女人等,然后有一些公共的特性可以在一些基础类中体现,而其他相似或者较小类可以直接使用上级类中的公共代码。

  1. 继承的基础:子类(要继承其他类的类,也称之为派生类)与父类(被继承类,也称之为基类)之间本身是一种包含于被包含关系,如此才有可继承的前提
1
2
3
4
5
//大类
class Human{} //人类
//小类
class Man{}
class Woman{} //属于人类包含的部分,可以继承Human
  1. 继承关键字:extends,子类想要继承父类,则必须在子类结构申明时明确使用extends关键字来继承相关类
1
2
3
4
5
//父类
class Human{} //人类
//子类
class Man extends Human{}
class Woman extends Human{} //继承Human类
  1. 继承效果:子类可以不用自己去实现某些功能,而可以直接访问父类中已经存在的成员
1
2
3
4
5
6
7
8
9
10
11
12
//父类
class Human{
public function eat(){
echo '吃饭';
}
}
//子类
class Man extends Human{} //子类为空类:没有类成员

//实例化子类对象
$m = new Man();
$m->eat(); //输出吃饭

总结

  1. 继承extends是面向对象思想中实现代码重复利用的重要特性
  2. 继承是指子类可以直接访问父类中已经存在的成员
  3. 继承可以节省代码工作,同时允许子类中进行扩展,即在子类中增加必要的父类不存在的功能

3. 多态【了解】

定义:多态,是指在发生类的继承的情况下,同时出现方法的重写(override),即子类拥有与父类同名的方法。然后在实例化对象的时候让父类对象指向子类对象(强制类型,PHP不支持),父类对象表现的子类对象的特点。

  1. 多态需要强类型语言,以Java为例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//父类
class Animal{
public void show(){
System.out.println("Animal");
}
}

//子类
class Dog extends Father{
//重写show方法
public void show(){
System.out.println("Dog");
}
}

//实例化:Java是强类型,必须指定保存数据的变量的类型
Animal a = new Dog(); //父类对象指向子类对象空间
a.show(); //打印Dog,父类对象调用的是子类方法
  1. PHP是弱类型语言,所以不存在变量的强制类型,因此PHP不支持多态。

总结

  1. 多态的发生必须是有继承关系,并且子类要重写父类方法
  2. 多态是指父类对象拥有子类形态,并且可以表现出子类的特性(调用子类方法)
  3. PHP是弱类型语言,不支持多态

二、 PHP继承

思考:PHP不支持多态,那么PHP中的继承是如何实现,以及到底是个什么效果呢?

引入:PHP是一种由面向过程过度到面向对象的编程语言,所以有一些自己的特性。总体而言,PHP中的继承与大多数面向对象语言一样。

1. 继承相关概念和实现【掌握】

定义:继承extends,是指子类通过继承可以访问父类的成员。

  1. 继承基本语法:class 子类 extends 父类{}
1
2
3
4
5
//父类(基类)
class Human{}

//子类(派生类)
class Man extends Human{}
  1. 继承效果:父类被继承的内容,可以通过子类对象进行访问(只能是子类对象)
1
2
3
4
5
6
7
8
9
10
11
12
13
//父类(基类)
class Human{
public function showName(){
echo __CLASS__;
}
}

//子类(派生类)
class Man extends Human{}

//实例化子类对象:如果实例化父类对象Human,那么与子类和继承毫无关系
$m = new Man();
$m->showName(); //访问继承自父类的方法
  1. 继承目标:继承本质是针对同类有包含关系的共性继承,即父类通常是包含子类,子类属于父类。所以在父类中通常定义的是子类共有的一些特性成员,这是开发者默认遵循的规则
1
2
class Animal{}
class Man extends Animal{} //不会有语法错误,也可以继承,但是不符合实际

总结

  1. 继承是利用extends进行子类和父类的关联
  2. 继承利用extends关键字实现
  3. 继承是指实现继承关系后,子类对象可以访问父类被继承的成员。而父类对象不可以访问子类成员(没有关系)

思考:类中有三大类成员:属性、方法和类常量,还有访问修饰限定符限制,那么继承是子类可以使用父类所有的成员吗?

引入:从理论上讲,继承后子类是可以访问父类的所有成员的,但是实际上父类有一些成员是并不希望被子类访问的,所以继承是有限的。

2. 有限继承【掌握】

定义:有限继承,指子类在继承父类的成员的时候,并非继承所有内容,而是继承并使用父类部分内容。

  1. 继承内容:PHP中继承是子类继承父类所有的公有成员受保护成员私有属性,不能继承父类的私有方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//父类
class Human{
const CALL = '人';
public $name = 'human';
protected $age = '100';
private $money = '100';

public function showName(){
echo $this->name;
}

protected function showAge(){
echo $this->age;
}

private function showMoney(){
echo $this->money;
}
}

//子类
class Man extends Human{}
//实例化子类
$m = new Man();
var_dump($m); //可以看到父类私有属性
$m->showName(); //允许直接访问:方法为公有允许类外访问
  1. 受保护继承protected,protected关键字的产生本身就是纯用于继承的,表示允许被子类在子类内部访问的意思,而不允许被外部直接访问。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//父类
class Human{
protected $age = '100';
protected function showAge(){
echo $this->age;
}
}

//子类
class Man extends Human{
//在子类内部增加公有访问访问继承自父类的受保护成员
public function getProtected(){
echo $this->age; //访问父类受保护属性
$this->showAge(); //访问父类受保护方法
}
}

//实例化子类对象
$m = new Man();
$m->getProtected(); //正确输出:说明可以访问
  1. 访问父类私有成员:子类若想访问父类私有成员,那么前提是父类提供了能够访问私有成员的接口:即提供了公有或者受保护的方法给子类访问
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//父类
class Human{
private $age = 100;
private $money = 100;
//提供接口供子类访问:此时通常是受保护的方法,肯定不允许外部直接访问的
protected function getAge(){
echo $this->age;
}
}

//子类
class Man extends Human{
//依然需要定义方法来访问继承自父类的受保护的成员
public function showPrivate(){
$this->getAge();
}
}

//实例化子类对象
$m = new Man();
$m->showPrivate(); //输出100,表示正确访问

注意:虽然子类可以通过以上方式来实现访问父类的私有成员(包括私有方法),但是从设计的意义上讲,私有就是不允许外部访问,所以父类通常不会提供对外的访问接口,以上案例只是为了知识说明。

  1. 静态成员(类常量)也遵循继承规则(PHP继承本质是对象),只是访问方式是由类进行访问
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Human{
const NAME = '人';
public static $count = 0;
protected static $type = array('黑','黄','白');

public static function getCount(){
echo self::NAME;
echo self::$count;
}
protected static function getType(){
print_r(self::$type);
}
}

class Man extends Human{
//依然需要子类中访问受保护成员
public static function getHuman(){
Human::getType();
}
}

echo Man::$count; //允许直接访问
Man::getCount(); //访问父类静态方法
Man::getHuman(); //利用子类公有方法访问父类受保护成员
  1. 构造方法和析构方法也可以被子类继承,此时需要注意子类对象实例化时对应的父类构造方法的参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//父类
class Human{
private $money;
public function __construct($money){
$this->money = $money;
}

public function __destruct(){
echo 'die';
}
}

//子类继承
class Man extends Human{}

//子类实例化:自身是空类,没有指定构造方法
//$m = new Man(); //错误:缺少参数,因为会自动调用父类构造方法
$m = new Man(100); //正确

总结

  1. 继承是有限继承,理论上是用来继承父类允许被继承的部分,即使用public或者protected修饰的成员
  2. 因为对象的属性是保存在对象内存空间,所以父类的私有属性也会继承
  3. 父类私有成员本质不允许被子类访问,但是可以通过父类开放接口实现(一般不会这么操作)
  4. 静态成员也可以遵循继承规则
  5. 构造方法也可以被继承,因此在实例化子类对象的时候,要考虑到父类构造方法所使用到的参数问题

思考:子类可以从父类进行继承,那如果子类与父类的成员重名了怎么办?

引入:首先,类中定义的成员是允许出现与其他类同名的,但是这个过程如果出现了继承,那么重名就存在重写的问题。

3. 重写Override【掌握】

定义:重写,即子类中定义了与父类重名的成员,子类可以重写父类任意类成员,通常重写是用来重写父类的方法,用于扩展或者更改某些业务逻辑。

  1. 子类继承父类,同时子类定义与父类同名的类成员
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//父类
class Human{
public $name = 'Human';
public function show(){
echo __CLASS__,'<br/>';
}
}

//子类继承
class Man extends Human{
//定义同名属性
public $name = 'Man';
//定义父类同名方法
public function show(){
echo __CLASS__,' hello world<br/>';
}
}
  1. 重写父类成员之后,子类只会直接访问子类的成员(覆盖)
1
2
3
4
5
//接上述代码
//实例化子类对象
$m = new Man();
$m->show(); //输出Human hello world
var_dump($m); //只有子类属性$name

注意:不管是公有和是受保护属性,一旦重写,父类的就会不存在,而私有属性不会被覆盖而丢失

  1. 重写的要求1:子类重写父类的方法,控制权不能高于父类,即子类可以比父类更开放
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//父类
class Human{
protected function show(){
echo __CLASS__,'<br/>';
}
}

//子类继承
class Man extends Human{
//重写
protected function show(){} //正确
public function show(){} //允许
private function show(){} //错误:控制权比父类更严格



}
  1. 重写的要求2:PHP中重写要求子类重写父类方法的时候,必须保证与父类同名方法参数一致
1
2
3
4
5
6
7
8
9
10
11
12
13
//父类
class Human{
protected function show(){
echo __CLASS__,'<br/>';
}
}

//子类继承
class Man extends Human{
//重写
public function show(){}
public function show($a){} //错误,与父类同名方法不一致
}

注意:在方法参数一致不单单要求数量一致,而且数据类型要求也必须相同,但形参名字可以不同;另外,在PHP7以前重写对于参数这块没有要求。

  1. 重写的要求3:重写针对的是被继承的成员,父类私有方法不会被继承,因此不受要求2规定
1
2
3
4
5
6
7
8
9
10
11
12
13
//父类
class Human{
private function show(){
echo __CLASS__,'<br/>';
}
}

//子类
class Man extends Human{
private function show($name){ //不会报错,因为本质不存在重写(父类Human::show没有被继承)
echo $name,'<br/>';
}
}
  1. 重写是指子类拥有特殊的情况,一般是需要在父类的基础上进行扩展,此时如果想要继续保证父类被重写的方法继续执行(默认永远只访问子类重写的新方法),需要在子类重写方法的时候使用parent关键字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//父类
class Human{
protected function show(){
echo __CLASS__,'<br/>';
}
}

//子类继承
class Man extends Human{
//重写
public function show(){
//强制调用父类被重写方法
parent::show();

//扩展业务逻辑
echo __CLASS__,'<br/>';
}
}

注意:parent不能访问父类的属性,可以访问静态属性、静态方法、类常量和普通方法

总结

  1. 重写override是一种在子类中定义父类同名成员的操作
  2. 公有、受保护的属性重写是直接覆盖父类成员,私有属性不会被覆盖;公有、收保护的方法会被重写,但是私有方法不会被重写(私有方法本质没有被继承)
  3. 重写的要求
  • 子类控制权不能高于父类控制权
  • PHP7中要求被重写的方法必须与父类保持参数一致(数量和类型)
  1. 方法被重写后,访问调用的都是子类方法,如果想要访问父类方法,可以通过在子类方法中使用parent关键字来强制访问父类方法
  2. parent不能用于访问父类的属性(静态属性可以)

思考:PHP继承与其他面向对象语言的继承有不同吗?

引入:PHP因为是自面向过程发展而来,所以并非完全使用面向对象的思想,因此PHP的继承有一些自己的特点。

4. PHP继承特点【掌握】

定义:PHP继承与其他纯面向对象(从设计之初就完全由面向对象思维支配)编程语言是有一些不一样的。

  1. PHP中继承只能单继承:即子类只有一个父类(有些语言支持多继承)
1
2
3
class Man{}
class Woman{}
class Ladyboy extends Man,Woman{} //PHP中错误,不允许继承多个父类
  1. PHP若想继承多个类,可以使用链式继承
1
2
3
class Man{}
class Woman extends Man{}
class Ladyboy extends Woman{} //Ladyboy包含了Man和Woman类中所有可继承的成员
  1. PHP中继承只有私有方法不能被继承
  2. PHP允许子类继承父类的构造方法和析构方法

总结:PHP中的继承与传统的面向对象继承有着一些小区别,大家在使用继承的时候要严格遵守PHP的继承规则


思考:self关键字是用于在类内部代替类的,代替的是当前方法所在的类本身,随着继承的实现,如果子类在访问父类的方法的时候,self到底代替的是谁呢?

引入:self是一种静态绑定,换言之就是当类进行编译的时候self已经明确绑定了类名,因此不论多少继承,也不管是子类还是父类自己来进行访问,self代表都是当前类。如果想要选择性的来支持来访者,就需要使用静态延迟绑定

5. 静态延迟绑定【掌握】

定义:静态延迟绑定,即在类内部用来代表类本身的关键字部分不是在类编译时固定好,而是当方法被访问时动态的选择来访者所属的类。静态延迟绑定就是利用static关键字代替静态绑定self,静态延迟绑定需要使用到静态成员的重写

  1. 静态延迟绑定:使用static关键字代替self进行类成员访问
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//父类
class Human{
public static $name = 'Human';
public static function showName(){
//静态绑定
echo self::$name,'<br/>';
//静态延迟绑定
echo static::$name,'<br/>';
}
}
//子类
class Man extends Human{
//重写父类静态属性
public static $name = 'Man'; //静态属性因为存储在类内部,因此不会覆盖
}

//子类访问
Man::showName(); //输出Human和Man
  1. 静态延迟绑定一定是通过继承后的子类来进行访问才有效果
1
2
//接上述代码
Human::showName(); //输出Human和Human(此时没子类的事儿)

总结

  1. 静态延迟绑定是指通过static关键字进行类静态成员的访问,是指在被访问时才决定到底使用哪个类
  2. 静态延迟绑定对比的是静态绑定self
  3. 静态延迟绑定的意义是用来保证访问的静态成员是根据调用类的不同而选择不同的表现

思考:继承是利用了一些共性以及共性大小来拆分的父类和子类,父类的目的是为了给子类提供一些共性代码,子类可以从父类继承,而且还可以有子类无限继承子类。继承的目的一是利用父类提供的公共代码,二是实现自身需求的扩展,如果子类无限被继承会有什么效果呢?

引入:事务的划分首先不可能无限细化,因此肯定是不需要无限的出现子类的。一旦无限出现子类,那么就会导致代码的执行效率大大降低;而且可能被某些无关类继承从而出现安全问题,但是从代码设计的角度来讲是无法限制开发者的。因此PHP提供了一种终结机制,让类无法被继承。

6. 最终类Final【掌握】

定义:最终类,使用final关键字修饰类名,表示此类不可以被继承。

  1. 基本语法:final class 类名
1
2
//最终类
final class Man{}
  1. 最终类无法被继承
1
2
3
4
//最终类
final class Man{}

class Man18 extends Man{} //致命错误:无法从final类继承
  1. final关键字不止修饰类表示类不可被继承,还能修饰方法,表示方法不能被重写
1
2
3
4
5
6
7
8
9
10
11
//父类
class Human{
public function show(){} //普通方法
public final function walk(){} //最终方法
}
//子类
class Man extends Human{
//重写
public function show(){} //没问题
public function walk(){} //致命错误:不能重写父类中的最终方法
}

总结

  1. final关键字修饰的类表示无法被继承
  2. final关键字还可以修饰方法,表示方法不能子类重写(通常类不会使用final关键字)
  3. final修饰类表示不希望类再出现子类,可以很好保护类的内部结构不被暴露
  4. final修饰方法表示不希望方法被修改,可以在一个更高的维度来保证同类事务的共同表现

思考:在一个项目中,如果架构师在前面给定义了一些父类,而且希望所有的子类都遵循父类的设置,有没有什么办法可以限制呢?

引入:理论上来讲没有系统性的办法能够严格去规范程序员必须怎么实现,但是大家如果都遵循规则的话,那么可以利用父类来强制规范子类必须完成要做的事情,这个时候需要用到抽象类

7. 抽象类Abstract【掌握】

定义:抽象类,使用abstract关键字修饰的类,表示该类只能被继承,不能被实例化

  1. 基本语法:使用abstract关键字修饰类
1
2
//抽象类
abstract class Human{}
  1. 抽象类无法被实例化
1
2
3
//抽象类
abstract class Human{}
$h = new Human(); //致命错误,抽象类不能被实例化
  1. 抽象类只能被继承
1
2
3
4
5
//抽象类(父类)
abstract class Human{}

//子类
class Man extends Human{} //正确
  1. abstract关键字还可以用来修饰方法(抽象方法),abstract修饰的方法不能有方法体,而且有抽象方法的类必须声明为抽象类
1
2
3
4
5
6
//抽象方法抽象类
abstract class Human{
//定义抽象方法:没有方法体
abstract public function eat();
public function show(){} //普通方法有方法体
}
  1. 抽象方法因为要被子类继承实现,所以不能使用private修饰(私有方法不会被继承)
1
2
3
4
5
//抽象类
abstract class Human{
//抽象方法
abstract private function eat(); //错误:抽象方法不能私有化
}
  1. 子类继承抽象类后,如果抽象类中有抽象方法,那么子类必须选择自己成为抽象类或者实现抽象方法(所有抽象方法)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//抽象方法抽象类(父类)
abstract class Human{
//定义抽象方法:没有方法体
abstract public function eat();
public function show(){} //普通方法有方法体
}

//子类1:抽象类继承抽象类
abstract class Man extends Human{} //正常继承
//子类2:子类实现父类所有抽象方法
class Boy extends Man{
//实现从祖父类继承的eat抽象方法
public function eat(){
echo 'eat';
}
}

总结

  1. 使用abstract修饰的类叫做抽象类
  2. 抽象类不可以被实例化,只能被继承
  3. 因为抽象类无法被实例化,因此私有成员在类中没有实质意义(还需要额外提供受保护或者公有方法来实现访问)
  4. 抽象类的目的是用来规范子类(通常必配抽象方法)
  5. abstract还可以修饰方法,称之为抽象方法:抽象方法所在的类必须是抽象类,抽象方法不能有方法体
  6. 有抽象方法的抽象类被继承时子类要么自身是抽象类,要么实现所有抽象方法
  7. 抽象类这种结构管理,需要耗费较多的架构和初始代码,通常在比较大型或者规范的项目中才会使用

思考:在大型项目中,如果想要规定某些类必须有某些方法,我们可以使用抽象类来进行规范吗?

引入:抽象类的目的就是用来规范一些共性子类的结构,必须实现某些指定的方法的。在PHP中,还有一种专门用来规范类内部实现方法的结构,叫做接口

8. 接口Interface【掌握】

定义:接口,使用interface关键字定义,与类类似,专门用来规范一些共性类必须实现的方法

  1. 接口定义:使用interface关键字,后跟接口名字(与类结构一样)
1
interface Human{}
  1. 接口不是类,不可以被实例化
1
new Human();				//致命错误,接口不能被实例化
  1. 接口实现:接口是用来规范类必须完成的事情,所以接口只能被类实现:implements
1
class Man implements Human{}
  1. 接口成员:接口中只能定义公有抽象方法和接口常量
1
2
3
4
5
6
7
8
9
10
11
12
interface Human{
//接口常量
const NAME = '人';
//接口抽象方法
public function eat();

//错误示例
public function go(){} //错误:接口中的方法必须为抽象
public $age; //错误:接口中不能有属性
public static $count = 0; //错误:接口中不能有静态属性(成员属性)
protected function walk(); //错误:接口方法必须为公有抽象方法
}
  1. 接口成员方法必须被实现的子类实现或者类为抽象类,接口常量可以直接在实现类中方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface Human{
//接口常量
const NAME = '人';
//接口抽象方法
public function eat();
}

//实现接口
class Man implements Human{
//必须实现接口所有抽象方法
public function eat(){
echo self::NAME; //可以访问接口常量
}
}

//抽象类实现接口
abstract class Ladyboy implements Human{} //正常实现
  1. 实现接口的类成员,不允许重写接口中的常量,不允许增加接口方法的控制权限
1
2
3
4
5
6
7
8
//接上述代码
class Woman implements Human{
//重写接口常量
const NAME = '女人'; //错误:不允许重写接口常量

//强化接口方法控制
private function eat(){} //错误:接口方法不允许使用其他访问修饰限定符,必须使用public
}
  1. 接口可以继承接口:extends,而且接口可以多继承接口
1
2
3
4
5
6
7
8
9
10
11
interface Human{
public function walk();
}

interface Animal{
public function eat();
}
//单继承
interface Man extends Human{}
//多继承
interface Ape extends Human,Animal{}

总结

  1. 接口是使用interface关键字定义,与类结构类似
  2. 接口只能被类实现
  3. 接口成员只有两种:接口常量和公有抽象方法(没有方法体:不需要abstract关键字)
  4. 类实现接口的时候,必须实现接口中所有的抽象方法(或者抽象类实现)
  5. 接口中的成员不能被实现接口的类进行重写(接口常量)和权限更高(受保护或者私有化接口方法)
  6. 接口可以继承接口,而且可以多继承
  7. 接口是在更大型的项目中,为了保证底层的实现而设定的规范,通常是抽象类实现接口,增加必要成员,然后让实际业务类去继承抽象类。

思考:PHP中继承是单继承,如果某个类有成员要被其他类使用,就需要成为其他类的父类才行,这样可能会导致继承链很长,合适吗?

引入:从继承的角度出发,继承链可以解决问题,但是的确效率会打折扣。同时,如果某些功能是共性使用,但是并不符合继承条件(不属于同一类事务),那么使用继承也有所违背面向对象规则,此时可以使用PHP提供的另外一种代码复用技术trait

9. trait代码复用【掌握】

定义Trait 是为类似 PHP 的单继承语言而准备的一种代码复用机制。trait可以使得单继承语言拜托为了复用而不得不继承的尴尬,让面向对象变得更加纯粹。

  1. trait是一种类似class的关键字
1
2
3
trait Eat{

}
  1. trait内部可以拥有一个类能拥有成员属性(包含静态),成员方法(包含静态),但不能有类常量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
trait Eat{
public $time;
protected $how; //允许定义,但是实际不用
private $info;

public function showTime(){
echo $this->time;
}
protected function showHow(){ //允许定义,但是实际不用
echo $this->how;
}

const PI = 3.14; //错误:trait中不能有常量
}
  1. trait是用来实现代码的复用的,不可以被实例化也不可以被继承(不是类)
1
2
trait Eat{}
new Eat(); //错误,没有该类
  1. trait是用来将公共代码提供给其他类使用的,而类要使用trait的前提是加载对应的trait
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
trait Eat{
public function show(){
echo 'eat';
}
}

//类中加载trait
class Human{
//加载:使用use关键字
use Eat; //use就表示将trait Eat中的所有东西拿到了当前类Human中
}

//使用trait中的内容
$h = new Human();
$h->show(); //eat:Human类自己没有show方法,但是因为使用了trait Eat,所以可用
  1. 一个类可以使用多个trait
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
trait t1{
public function eat(){
echo 'eat';
}
}

trait t1{
public function sleep(){
echo 'sleep';
}
}

class Human{
//使用多个trait
use t1,t2;
public function show(){
$this->eat();
$this->sleep();
}
}

$h = new Human();
$h->show(); //eat sleep
  1. 如果同时引入的多个trait中有同名方法,那么会产生冲突:解决冲突的方法是使用insteadof代替处理以及对被替代方法使用别名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
trait t1{
public function eat(){
echo 't1,eat';
}
}
trait t2{
public function eat(){
echo 't2,eat';
}
}

class Human{
use t1,t2; //错误:eat()方法冲突
}

//解决方案:明确替代
class Person{
use t1,t2{ //花括号
t2::eat insteadof t1; //t2的eat代替t1的eat
}
}
$p = new Person();
$p->eat(); //t2,eat

//解决方案:先替换后别名
class Animal{
use t1,t2{
t1::eat insteadof t2; //明确使用t1中的eat方法
t2::eat as eat2; //t2中的eat方法改名叫eat2
}
}
$a = new Animal();
$a->eat(); //t1,eat
$a->eat2(); //t2,eat
  1. 同名覆盖问题:如果类中有与引入的trait同名成员,会有不同处理
  • 属性:不允许重名,即类中不允许定义与trait中同名的成员属性(静态属性也一样)
  • 方法:类覆盖trait
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
trait Eat{
public $food = '米饭';
public function show(){
echo $this->food;
}
}

class Human{
use Eat;

//定义同名属性
//public $food = '面条'; //错误

//定义方法
public function show(){
echo 'show';
}
}

$h = new Human();
$h->show(); //show:类覆盖掉trait
  1. 继承覆盖问题:如果类中在使用trait的同时,也是继承自父类,而trait中与父类中有同名方法,那么trait中将覆盖父类同名方法;如果要访问父类方法,可以在trait同名方法中使用parent关键字访问父类同名方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
trait Eat{
public function eat(){
echo 'Eat::eat';
}
}
class Human{
public function eat(){
echo 'Human::eat';
}
}
//子类继承父类同时使用trait
class Man extends Human{
use Eat;
}

$m = new Man();
$m->eat();
  1. 另外,trait自己不能访问,只是用来给其他类提供代码复用的,因此允许类在使用trait时更高里面方法的访问控制权:在as之后,使用目标访问修饰限定符
1
2
3
4
5
6
7
8
9
10
11
12
13
trait Eat{
private function show(){
echo 'eat';
}
}
class Human{
use Eat{
show as public eshow;
//注意:as是用来设定别名的,虽然没有同名show,但是系统认为show已经存在,所以必须别名
}
}
$h = new Human();
$h->eshow(); //eat
  1. trait中可以使用抽象方法,用来规范使用类必须实现对应抽象方法:使用类要么为抽象类,要么就必须实现抽象方法
1
2
3
4
5
6
7
8
9
10
11
12
13
trait Eat{
public function eat(); //抽象方法
}
abstract class Human{
use Eat; //抽象类:可以不实现抽象方法
}

class Animal{
use Eat;
public function eat(){ //具体类:实现抽象方法
echo 'Animal::eat';
}
}

总结

  1. trait是一种类似class结构关键字,trait不能被实例化,可以拥有所有类成员结构(类常量不行)
  2. trait是用来实现代码复用的,为其他类提供公共代码(方法),其他类如果使用trait用use关键字引入
  3. 在类中use具体trait就相当于将trait内的所有代码在类中写了一遍
  4. 一个类可以使用多个trait,但是要注意同名问题
  • 同名方法可以使用insteadof来实现替代:一个trait中的同名方法替代另外一个,类就访问替代的那个
  • 同名方法可以在被替代之后使用as制作方法别名:类就可以拥有两个方法
  1. 类中在引入trait后,要注意与trait中的同名成员问题
  • 同名属性:不予许(类中不允许)
  • 同名方法:允许,类中的方法会覆盖trait中的方法
  1. 如果类在使用trait的同时也继承了其他类,那么trait中出现的同名方法会覆盖基类的同名方法:类自己 > trait > 基类
  2. 类在使用trait时可以修改trait方法的控制级别:更严或者更宽松都可以,注意修改控制级别时使用的是别名机制,一定需要改成别名:[trait名::]方法名 as 访问修饰限定符 别名;(原来trait中的方法依然可以正常使用)
  3. trait中可以使用抽象方法,那么使用该trait的类就必须本身为抽象类或者将抽象方法实现
  4. trait使用机制
  • 有公共代码要实现(方法),而这些方法可能在很多类中会用到
  • 公共代码不是属于某一类事务特有,而是很多事务都有(不符合继承)

思考:PHP对象是复合数据类型,如果直接echo输出对象会报错的,这样的报错会给用户体验不好,有没有办法能够解决这类问题呢?

引入:虽然上述问题出现是因为程序员的误操作,但是的确有可能会出现这样的问题,尤其是当我们的程序是给一些外部调用时。所以,面向对象关于很多对象可能出现的错误都提供了一种容错机制,这种机制叫做重载

10. PHP重载【了解】

定义:重载overload,本意指在一个类中可以出现多个同名方法,彼此之间的参数个数和类型不一样。但是PHP中不支持同名方法,而且也不区分数据类型(弱类型语言),所以PHP不支持传统重载。PHP中的重载指的是当某些不允许操作发生时,会自动调用的一种内部机制,即自动调用相关的魔术方法。

  1. 魔术方法:指系统为类中预先设计好的,只需要开发者实现的方法,魔术方法以双下划线__开始。对象在某些特定情况下会自动调用的方法。构造方法、析构方法和克隆方法就是魔术方法
1
2
3
4
class Man{
public function __construct(){}
}
new Man(); //实例化后对象自动调用(触发时机:对象被实例化)
  1. PHP重载是指某些容错处理(也可以理解为为了某些特殊情况而自动调用),在访问没有权限或者不存在的属性或者方法的时候,会自动触发的魔术方法。
  • 属性重载
  • 方法重载
  1. 属性重载:当PHP对象访问不存在的或者没有权限访问的属性的时候会自动调用的方法
  • __get($key):读属性的时候触发
  • __set($key,$value):写属性的时候触发
  • __isset($key):外部调用isset()函数或者empty()函数时自动触发
  • __unset($key):外部调用unset()结构删除对象属性时自动触发
  • __toString():对象被当做普通变量输出或者连接时自动调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Man{
private $age = 10;

//读取重载
public function __get($key){
echo $key,__METHOD__,'<br/>';
}

//写重载
public function __set($key,$value){
echo $key . ' : ' . $value . '<br/>';
}

//查是否存在重载
public function __isset($key){
echo $key,__METHOD__,'<br/>'
}

//删除属性重载
public function __unset($key){
echo $key,__METHOD__,'<br/>';
}

//对象字符串化
public function __toString(){
echo __METHOD__,'<br/>';
}
}
//实例化
$m = new Man();
$m->age; //类外访问私有属性:原本不允许
$m->age = 100; //设置
isset($m->age); //判定
unset($m->age); //删除
  1. 属性重载的目的:一方面为了不让程序运行出错,另一方面可以在类内部由我们自己控制内容的访问
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Man{
private $age = 10;

//读取重载
public function __get($key){
//echo $key,'<br/>';
//定义一个允许访问列表:假设有很多私有属性
$allow = array('age');
//判定是否在列表内:在就允许访问,不在就返回NULL或者false
if(in_array($key,$allow)){
return $this->$key; //可变属性:$key是外部访问的目标,最终为$this->age
}

//不允许访问
return false;
}

//写重载(该方法没有返回值)
public function __set($key,$value){
//echo $key . ' : ' . $value . '<br/>';
//与__get理论类似:允许的设置,不允许的不设置(什么都不做即可)
}

//判定重载
public function __isset($key){
//给出内部判定结果
return isset($this->$key);
}

//对象重载
public function __toString(){
//返回一个指定字符串(一般是当类有属性保存某些信息时,输出某个属性)
return __METHOD__;
}
}
  1. 方法重载:当PHP对象访问不存在的方法或者不允许访问的方法时自动调用的方法(抑或是谋者特殊情况,如构造方法)
  • __call($function_name[,$args]):对象调用不可调用方法时触发
  • __callStatic($function_name[,$args]):类访问不可调用静态方法时触发
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Man{
private function show(){
echo __METHOD__,'<br/>';
}
private static function staticShow(){
echo __METHOD__,'<br/>';
}

//普通方法重载
public function __call($name){
echo $name,__METHOD__,'<br/>';
}

//静态方法重载
public static function __callStatic($name){
echo $name,__METHOD__,'<br/>';
}
}

//访问不可访问的方法
Man::staticShow();
$m = new Man();
$m->show();
  1. 方法重载的主要目的:不让外部访问出错。当然,如果必要时也可以进行内部访问
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Man{
private function show(){
echo __METHOD__,'<br/>';
}
private static function staticShow(){
echo __METHOD__,'<br/>';
}

//方法重载
public function __call($name,$arg){
//允许访问列表
$allow = array('show');

//判定是否在列表中
if(in_array($name,$allow)) return $this->$name($arg);
//其他情况
return false;
}
public static function __callStatic($name){
//不允许访问
return false;
}
}

总结

  1. PHP重载不是指同名方法,而是指对象或者类在访问一些不允许或者不存在的属性或者方法的时候自动调用的魔术方法
  2. PHP重载分为属性重载和方法重载
  3. PHP重载的目的是为了保护程序的正确运行而提供的一种容错机制
  4. 并非所有类都需要实现这些重载,只是如果有类需要对外提供访问使用的时候才有必要采取

思考:如果我们想把对象内部的所有属性都输出出来,该如何实现呢?

引入:从业务的角度出发,一般不会有此需求:因为对象内部有私有、受保护和公有属性,而能够被外部访问的只有公有属性。如果只是单纯的想了解对象内部有哪些公有属性,可以使用foreach对对象进行遍历

11. 对象遍历foreach【了解】

定义:遍历对象,其实就是指将对象中的所有属性(公有属性)以键值对的形式取出并进行访问

  1. 使用foreach对对象进行遍历
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//定义类
class Man{
public $name = 'LiLei';
public $height = 178;
public $weight = 140;
protected $age = 30;
private $money = 1000;
}
//实例化
$m = new Man();
//遍历
foreach($m as $k => $v){
echo $k . ' : ' . $v . '<br/>'; //$k为属性名,$v为属性值
}

总结

  1. foreach可以对对象像数组一样遍历
  2. foreach遍历对象遍历的是对象内部的所有公有属性(在类外部进行对象遍历)