面向对象编程
PHP中的编程方式只有两种面向过程和面向对象。
OOP(Object Oriented Programming,面向对象程序设计)是一种高级的计算机编程架构。让我们更好的组织项目中的代码,目前所有流行的PHP框架都是面向对象的方式编写的,熟练掌握面> 向对象是PHP开发者的必备技能。
面向对象中的概念比较多,这里列出来的只是一小部分
以下需熟练掌握:
- 类和对象
- 命名空间
- 类的自动加载
- 魔术方法
- 静态成员
- 继承
类和对象
类
类
( Class ) 是 面 向 对 象 程 序 设 计 ( OOP , Object Oriented Programming)实现信息封装的基础。类是一种用户定义类型,也称类类型。每个类包含数据说明和一组操作数据或传递消息的函数。类的实例称为对象
面向对象编程有三大特性:封装
、继承
、多态
。其中封装的意思就是说所有的代码必须要写在一个类中,不能把代码写到类的外部。不过因为 PHP 并不是一个纯粹的面向对象的语言,PHP 中即支持面向过程的语法又支持面向对象的语法,所以在 PHP 中即使把代码写到类的外部也是可以的。
定义类的方法
1 | class 类{ |
说明:
- 一般类名的首字母大写,比如
class Blog
- 一般一个类写在一个文件中,文件名和类名相同,比如Blog.php文件中定义
class blog
- 在类中只包含,常量,属性。
访问类型
属性和方法包含三种访问类型public protected private
- public (公有的、默认的):所有的内部成员或外部成员都可以访问(读和写)这个类成员(包括成员属性和成员方法)
- private (私有的):被定义为private的成员,对于同一个类里的所有成员是可见的,即没有访问限制;但对于该类的外部代码是不允许改变甚至读操作,对于该类的子类,也不能访问private修饰的成员
- protected(受保护的):被修饰为protected的成员不能被该类的外部代码访问。但是对于该类的子类有访问权限,可以进行属性、方法的读及写操作,该子类的外部代码包括其的子类都不具有访问其属性和方法的权限。
对象
对象是对类的实例化
类只是描述了要实现的功能,类中的代码要想执行,需要实例化出对象来,通过对象我们才能真正执行类中的代码。
类就相当于图纸定义了类的功能,对象是根据图纸制作出来的具体的实体。一个类可以创建出任意多个对象:
类的实例化:
1 | new 类名; |
例化类的对象之后,我们就可以使用对象来访问类中的属性和方法了。
说明:
- 在其他文件中如想使用类,首先需要先引入类文件
- 类中的属性和方法都要使用 $对象-> 访问
- 类中的 常量 和 静态成员 直接使用 类名:: 来访问
注意:一个类能创建多个对象,一个对象只能是一个类创建的
$this
在类的外部我们使用 -> 来访问类中的属性和方法:
1 | $blog->title='haha' |
那么,在类的内部应该如何访问类中的属性和方法呢?
使用$this
在类中如何访问常量和静态成员呢?
使用self::
命名空间
我们开实际大型项目时,我们可能需要引入很多第三方的类库,那么这些类库中有没有可能出现同名的类呢?肯定会有!
为了解决类同名的问题,PHP 中引入了 ”命名空间“ 机制。
namespace
我们可以使用namespace
声明一个命名空间,然后在它后面定义的类就都属于这个命名空间,比如,我们声明一个叫做 test 的命名空间,并在空间中定义一个类:
1 | namespace text; |
注意:namespace
必须是文件中的第一行代码。
这个 Student 类现在就属于 test 这个命名空间中,这时当我们再要使用这个类时,必须要在前面加上命名空间:
1 | $student = new test\Student; |
注意:命名空间使用 \ 符号。
这样不同类包中的同名的类就不会冲突了,因为不同类包的命名空间都不相同,比如:腾讯公司的 Log 可能是属于 Tencent 命名空间下,阿里公司的类包可能是属于 Ali 命名空间下,即使它们的类名都叫 Log 也不会冲突:
1 | // 实例化阿里 Log 类 |
这就是命名空间的用途。
命名空间也可以有子命名空间:
1 |
|
在实际工作当中,我们一般声明命名空间和类文件所在的目录相同。
比如:在 app/controllers 目录下有一个 BlogController 类:
app/controllers/BlogController.php
1 |
|
总结
总结:
- 为了避免类同名,所以引入命名空间
- 命名空间和类文件所在目录相同
- namespace 必须是文件中的第一行代码
use
现在每次实例化一个类时,需要写很长的类名(包含命名空间),如何能简化呢?
方法一:在同一个命名空间下,可以省略命名空间
方法二:使用 use
同一命名空间下
在使用同一个命名空间里面的类时,可以省略命名空间,直接写类名。
比如,我们再创建一个 models/User
类:
models/User.php
1 |
|
因为这个类和同目录下的 Blog 类都在同一个命名空间 app\models
下,所以可以省略命名空间:
1 |
|
总结:同一命名空间下的类,在使用时可以省略命名空间,但记住还是需要引入类文件的。
use
当使用不同命名空间中的类时,需要在实例化时加上命名空间,这会导致需要输入的类名比较长:
1 | $blog = app\controllers\BlogController |
如果想要简化可以使用 use
语句。
use 的功能是在文件的开始引入一个命名空间下的类,引入之后就可以直接使用类名来使用这个类了:
1 | use app\controllers\BlogController; |
在引入时也可以为类起别名:
1 | use app\controllers\BlogController as BC; |
示例、创建 testUse.php 文件
1 |
|
类文件的自动加载
有没有发现我们每次在使用一个类时都要先使用 require
引入类文件,在一个大的项目中我们需要使用数十个类,如果每次都要引入数十个类文件,实在是太麻烦了。
为了解决这个问题,PHP 为我们提供了类的自动加载机制,有了这个机制,我们就可以直接使用类了,然后 PHP 会自动引入相应的类文件,我们就不用一个一个的手动引入了。
spl_autoload_register
PHP 中提供了一个 spl_autoload_register
函数,该函数可以让我们注册一个函数到 PHP 中,然后当我们使用一个类时,如果 PHP 找不到这个类,就会调用我们注册的函数来加载相应的类文件。
1 | 使用一个类 => 找不到?=> 调用注册的函数 => 加载类文件 => 找到了 => 类可以使用了 |
示例、注册一个类加载函数:
1 | // 定义加载函数 |
代码说明:
- 定义了一个 load 函数
- 注册到 PHP 中
- 当我们使用一个不存在的类时,load 函数会被调用,参数就是类的名字(包含命名空间)
示例、创建一个 testLoad 文件
testLoad.php
1 |
|
启动 PHP 服务器 php -S localhost:9999
然后浏览器中运行代码:
因为我们没有加载类文件,所以报错显示找不到类类,但同时也可以看到我们的 load 函数被执行了。
总结:当一个不存在的类被使用时,我们注册的函数会被自动调用。
魔术方法
在 OOP 中有一套特殊的方法,叫做魔术方法,它们的特点是:
- 方法名以两个下划线开头(__)
- 在某一时刻自动被调用
接下来我们来学习其中最重要的一个: __construct
其他的魔术方法在文末:
构造方法
__construct 是一个魔术方法,它在实例化一个类对象时会被调用,经常用来初始化类的数据,我们一般叫做:“构造方法”
自动调用
演示1、构造方法在实例化对象时被调用
每当使用 new
实例化一个类对象时,类中的构造方法就会被调用一次:
Boy.php
1 |
|
初始数据
构造方法经常用来初始化类中的数据。我们通过在构造方法中添加参数来接收初始的数据。
Boy.php
1 |
|
当一个类的构造方法上有参数时,在实例化这个类的对象时,必须要使用小括号依次设置参数值:
1 | $boy = new Boy('小明'); |
示例、统计一个类所拥有的对象的总数。
要统计一个类被实例化的总数,原理很简单:每次实例化一个对象时就把计数加1。我们可以把代码写在构造方法中,因为每次使用 new 创建对象时,构造函数都会被调用。
1 |
|
静态成员
在类中我们可以使用 static
定义静态成员。可以是静态属性也可以是静态的方法:
1 |
|
代码说明:
- 在方法或者属性前添加
static
来定义静态成员 - 在类外部使用
类名::
来访问静态成员,如Boy::$count
,Boy::getCount()
- 在类内部使用
self::
访问静态成员,如:self::$count
静态成员和普通成员区别?
静态成员:静态成员属于类,无论有多少个对象,值只有一个。
普通成员:普通成员属于具体的对象,每个对象拥有自己的属性值。
比如:学生姓名就应该是一个普通属性,属于每一个同学,因为每个同学有自己的名字。而学生总人数这个属性就应该是一个静态属性,它属于“学生”这个大类,并不是某一个具体学生的属性。
同理,像身高、体重、性别这些都应该是普通属性,而平均年龄,最大年龄等应该属于静态属性。
继承
继承是面向对象三大特性之一,通过继承我们可以避免编写重复的代码,让我们的代码管理起来更加有组织有层次。
继承:一个类可以继承自另一个类,继承之后就拥有了那个类中所有非私有的属性和方法。
extends
PHP 中使用 extends
实现继承。
1 |
|
代码说明
- 一个类只能继承自一个父类(单继承),不能同时继承多个类
- Boy 继承自 People ,所以 Boy 的类中就拥有了 name 属性和 eat 方法
避免重复代码
实际应用中,我们经常把多个类共有的方法制作成一个父类,然后让这个类继承自这个父类,这样相同的代码就只需要写一次:
动态绑定
继承时有一个特性:“动态绑定”,在实际应用中经常会用到,接下来我们来学习一下到底什么是动态绑定。
首先我们先来看一段代码:
1 |
|
请问:最后两行分别输出的是什么?
$boy->getName()
输出的是 “jack”。
$people->getName()
输出的是 “tom”。
它们执行的代码是一样的,都是 echo $this->name
,为什么输出的结果却不同呢?
要知道原因,就要先搞清楚 $this
到底代表什么?
$this
代表实例化的那个对象。
当 new People
时,$this
就代表 People 类的对象,所以得到的是 People 类中的 name。
当new Boy
时, $this 代表 Boy 类的对象,所以得到的就是 Boy 类中的 name。
总结:$this
的值是动态的,所以叫做动态绑定,即:$this->name
的值到底是什么我们在编写类时并不确定,只有在使用 new
实例化对象时才能确定。
练习:以下代码的输出结果是?
1 |
|
Trait
Trait:特质,可以让我们不使用继承就可以在多个类中复用方法的机制。
定义特质
使用 trait
来定义一个特质,特质中只能定义方法:
1 | trait Fly |
使用特质
定义了特质之后,我们就可以在一个类中使用 use
来引入这个特质,引入了特质之后,这个类就拥有了这个特质中的方法。
1 | class Superman |
总结:
- trait 可以用来向一个类中添加方法
- 不用继承就可以实现方法的复用
魔术方法:
构造函数:__construct():
构造函数是类中的一个特殊函数,当我们使用new关键字实例化对象时,相当于调用了类的构造函数。
析构函数:__destruct():
①析构函数在对象被销毁释放之前自动调用;
②析构函数不能带有任何的参数;
③析构函数常用于对象使用完以后,释放资源,关闭资源等。__set($key,$value):
给类私有属性赋值时自动调用,调用是给方法传递两个参数:需要设置的属性名、属性值
__get($key):
给获取类私有属性时自动调用,调用是给方法传递一个参数:需要获取的属性名isset($key):
外部使用isset()函数检测私有属性时,自动调用。
类外部使用isset();检测私有属性,默认检测不到(false)
所以,我们可以使用isset()函数,在自动调用时,返回内部检测结果__unset($key):
外部使用unset()函数删除私有属性时,自动调用;__clone:
① 当使用clone关键字,克隆对象时,自动调用clone函数
② __clone()函数类似于克隆是使用的构造函数,可以给新克隆对象赋初值
③ 克隆函数里面的$this指的是新克隆的对象__tostring()
当使用echo等输出语句,直接打印对象时,调用 echo $zhangsan;那么, 可以指定tostring()方法的返回值,返回值需要是字符串。
则使用echo函数打印时,将会打印出tostring()函数返回的字符串__call()
调用类中未定义或未公开的方法时,会自动执行__call()方法,自动执行时,会给call方法传递两个参数:
① 调用的方法名
② (数组)调用的方法的参数列表__autoload()
① 这是唯一一个不在类中使用的魔术方法
② 当实例化一个不存在的类时,自动调用这个魔术方法
③ 调用时,会自动给autoload()传递一个参数:实例化的类名
所以,可以实现 使用这个方法,自动加载类文件的功能:
$zhangdan=new Person(“战三”);
//本文件没有Person类,会自动执行autoload加载person.class.php文件
复制代码sleep():
① 当执行对象串行化(将对象通过一系列操作,转化为字符串的过程,称为串行化)的时候,会自动执行sleep()函数;
② __sleep()函数要求返回一个数组,数组中的值,就是可以串行化的属性, 不在数组中的属性,不能被串行化。wakeup()
① 当反串行化对象时,自动调用wakeup()方法;
②自动调用时,用于给反串行化产生的新对象的属性,进行重新赋值;