PHP 类与对象、访问控制
概述
PHP 5 完全重写了对象模型,从而使得自 PHP 5 开始,PHP 具备了完整的面向对象编程能力。面向对象编程(即 Object Oriented Programming,简称 OOP)是一种计算机编程架构,和基于函数构建程序(也被称作函数式编程)不同,面向对象编程的思想是在程序中包含各种独立而又相互调用的对象,每一个对象都应该能够接受数据、处理数据(通常通过对象方法实现)并将数据传达给其它对象,当我们下达指令时,不再是调用函数,而是指定对象的方法。因此,在面向对象编程中,对象是程序的基本单元,一个对象包含了数据和操作数据的函数。
面向对象编程中最核心的概念就是类(Class)和对象(Object),类是对象的抽象模板,而对象是类的具体实例,比如「Laravel 精品课」是一个课程,那么课程就是一个类,而「Laravel 精品课」是这个类的一个实例,或者更直观一些,我们把学生看作一个类,那么具体的某个学生,比如张三、李四就是学生类的实例。对象包含的数据称之为类属性(Property),操作数据的函数称之为类方法(Method)。
我们还可以从另一个角度来看类和对象,以 PHP 为例,基本数据类型包括整型、浮点型、字符串、布尔类型、数组,对于整型这个类型而言,1、2、3、4、5 这些具体的数字就是它的实例(对象),我们也可以把自定义的类看作一个数据类型,只是这个类型是基本类型之外的真实世界复杂类型的映射,比如学生、课程、汽车、飞机、学校、公司、项目等等,对于学生这个类型而言,每一个个体的学生就是它的实例(对象)。
编写自定义类
准备工作:在
php_learning
目录下创建oop
子目录存放本章节代码。
所以要创建对象,需要先定义对应的类,我们以汽车为例,编写一个自定义的类 Car
,在 php_learning/oop/class.php
中编写这个类:
可以看到,在 PHP 中,类通过关键字 class
进行声明,然后紧跟着类名 Car
(通常我们通过首字母大写来定义类名),然后另起一行,通过一对花括号定义类的具体属性和方法。目前,该类是一个空实现,没有定义任何属性和方法。
但是我们已然可以通过 PHP 内置的 class_exists
方法判断该类是否存在:
if (class_exists('Car')) {
echo "class Car exists." . PHP_EOL;
} else {
echo "class Car not exists." . PHP_EOL;
}
类属性
接下来,我们可以为这个类定义一些属性,这些属性可以是变量,也可以是常量:
<?php
/**
* Class Car
*/
class Car
{
const WHEELS = 4; // 汽车都是4个轮子
var $seats; // 座位
var $doors; // 门
var $engine; // 发动机
var $brand; // 品牌
}
这里我们通过 var
来定义变量属性,通过 const
来定义常量属性,由于汽车都是4个轮子,所以我们通过常量 WHEELS
来定义(大写、无 $
前缀),而座位数、门、发动机、品牌都是可变的,所以通过变量进行定义。
类方法
有了属性之后,可以通过方法进行设置和获取,以 $brand
为例,在 PhpStorm 中,可以通过如下方式快速为其生成设置(Setters)和获取(Getters)方法:在 Car
类的花括号中,右键->从下拉菜单选择 Generate(或者通过对应快捷键呼出窗口):
在弹出窗口选择「Getters and Setters...」:
选择指定属性生成设置和获取方法:
点击「OK」,就可以在类中生成对应的 Setters 和 Getters 方法了:
<?php
/**
* Class Car
*/
class Car
{
const WHEELS = 4; // 汽车都是4个轮子
var $seats; // 座位
var $doors; // 门
var $engine; // 发动机
var $brand; // 品牌
/**
* @return mixed
*/
public function getBrand()
{
return $this->brand;
}
/**
* @param mixed $brand
*/
public function setBrand($brand): void
{
$this->brand = $brand;
}
}
除此之外,还可以编写其他自定义方法,比如汽车的最基本功能 —— 开车,我们为此定义一个 drive
方法:
/**
* 开车
*/
public function drive()
{
echo "1.启动引擎..." . PHP_EOL;
echo "2.挂D档..." . PHP_EOL;
echo "3.放下手刹..." . PHP_EOL;
echo "4.踩油门,出发..." . PHP_EOL;
}
与之对应的,还可以定义一个熄火方法 —— close
:
/**
* 熄火
*/
public function close()
{
echo "1.踩刹车..." . PHP_EOL;
echo "2.挂P档..." . PHP_EOL;
echo "3.拉起手刹..." . PHP_EOL;
echo "4.关闭引擎..." . PHP_EOL;
}
实例化对象
有了这些基本的类属性和方法后,就可以基于这个类创建具体的对象并调用对象方法执行任务了,我们通常将基于类创建对象的过程称之为实例化,在 PHP 中,我们通过 new
关键字进行类的实例化:
$car = new Car();
然后就可以操作类属性或者调用类方法了,类常量值不可更改,只能访问,在类外面访问类常量,需要通过类名 + ::
+ 常量名的方式:
var_dump(Car::WHEELS);
由于常量是类级别的,无需实例化即可访问。而对于对象级别的类属性(变量类型),需要通过实例化后的对象才能访问,并且访问之前,需要先设置:
$car->seats = 5;
var_dump($car->seats);
当然,如果提供了 Setters/Getters 方法,可以通过这些方法进行设置/获取,从而屏蔽实现细节:
$car->setBrand("奔驰");
var_dump($car->getBrand());
要访问类方法,直接通过对象实例 + ->
+ 方法名即可:
$car->drive();
$car->close();
可以看到,在 PHP 中,对象级别的属性和方法,都是通过箭头符 ->
进行访问的。
上述所有代码的打印结果如下:
构造函数
上述对象实例化是通过 new Car()
来实现的,这段代码实际上调用了 Car
的缺省构造函数,构造函数的用途是在对象实例化过程中调用,用于对该对象进行一些初始化操作,因此,我们可以借助显示编写构造函数对 Car
对象属性进行初始化。
在 PhpStorm 中,要为某个类编写构造函数,依然可以通过模板代码来实现,在 Car
的花括号范围内,通过右键->从下拉菜单选择 Generate->在呼出窗口选择「Constructor」:
然后在呼出窗口选择要设置的属性字段(我这里全选):
点击「OK」即可生成对应的构造函数:
/**
* Car constructor.
* @param $seats
* @param $doors
* @param $engine
* @param $brand
*/
public function __construct($seats, $doors, $engine, $brand)
{
$this->seats = $seats;
$this->doors = $doors;
$this->engine = $engine;
$this->brand = $brand;
}
也可以将其调整为如下的参数列表,处理品牌之外,其他参数均包含默认值:
public function __construct($brand, $seats = 5, $doors = 4, $engine = 1)
{
$this->seats = $seats;
$this->doors = $doors;
$this->engine = $engine;
$this->brand = $brand;
}
因为对于绝大多数汽车而言,都是 5 个座位、4 个门、1 个发动机,这里需要注意的是 $this
变量,它指向的是当前对象实例引用,可以用于在类内部调用对象级别属性和方法(类级别用 self::
访问,后面讲静态属性和方法时会介绍),除了构造函数之外,普通类方法中也可以使用 $this
:
/**
* 开车
*/
public function drive()
{
echo "1.启动引擎..." . PHP_EOL;
echo "2.挂D档..." . PHP_EOL;
echo "3.放下手刹..." . PHP_EOL;
echo "4.踩油门..." . PHP_EOL;
printf("5.%s汽车已出发\n", $this->brand);
}
/**
* 熄火
*/
public function close()
{
echo "1.踩刹车..." . PHP_EOL;
echo "2.挂P档..." . PHP_EOL;
echo "3.拉起手刹..." . PHP_EOL;
echo "4.关闭引擎..." . PHP_EOL;
printf("5.%s汽车已熄火\n", $this->brand);
}
这样一来,我们就可以通过下面这段代码来初始化新的 Car
对象:
$car = new Car("奔驰");
然后再访问 $car
对象的属性和方法:
var_dump($car->getBrand());
var_dump($car->seats);
var_dump($car->doors);
var_dump($car->engine);
$car->drive();
$car->close();
打印结果如下:
访问控制
最后,我们来看看 PHP 中类属性和方法的访问控制,在 PHP 中,类属性和方法的访问控制作用域是当前类与继承类中,关于类的继承,学院君会在下篇教程中介绍。
具体来说,PHP 通过 public
(公开)、protected
(保护)、private
(私有)关键字控制类属性和方法的可见性:
- 对于通过
public
声明的属性和方法,在类以外和继承类中均可见; - 对于通过
protected
声明的属性和方法,仅在继承类(支持多个层级)中可见,在类以外不可见; - 对于通过
private
声明的属性和方法,仅在当前类内部可见,在继承类和类之外不可见。
所谓的「可见」与「不可见」,是一种形象的说法,实际含义是可访问可设置。我们前面定义的类方法都是通过 PhpStorm 自带模版生成的,默认都是 public
声明,对于构造函数来说,除了单例模式这种特殊场景,其他都是需要通过 public
声明,否则在类以外不可见影响对象实例化。对于操作属性的 Getters/Setters 方法通常用于从外部处理 private
类型属性,所以也需要声明为 public
,其他的场景可以根据具体业务场景需求来。
我们之前通过 var
声明类属性,这是比较老的用法,是为了向后兼容 PHP 4,在 PHP 5 中,通过 var
声明的属性和方法统统被视作 public
,所以我们在测试代码中可以从外部直接访问和设置,下面,我们将其都设置为 protected
类型,以便在当前类和继承类中可见,在类以外不可见,从而保护对应属性不被恶意修改:
protected $seats; // 座位
protected $doors; // 门
protected $engine; // 发动机
protected $brand; // 品牌
如果你不想某些属性在子类中被访问和修改,可以将其进一步限制为 private
类型,所有类方法也是同理,这里不再独立演示。
这个时候,在 PhpStorm 中,可以看到之前在类外部直接访问类属性的代码会报错:
提示类属性访问类型为 protected
,不能从外部直接访问,直接通过 Setters/Getters 间接访问,如果要设置的话,请参照上述 brand
属性的设置。
No Comments