关注这个专栏的其他相关笔记:[Web 安全] 反序列化漏洞 - 学习笔记-CSDN博客
因为后面介绍的 PHP 反序列化漏洞其实与 PHP 对象是密不可分的,所以这边笔者就简单介绍一下 PHP 中面向对象编程的基础知识。这里笔者是假定读者已经对 PHP 有基础了解了,且本地已经安装了 PHP 环境的情况。
0x01:PHP 面向对象编程 — 基础入门
面向对象(Object-Oriented,简称 OO)是一种编程思想和方法,它将程序中的数据和操作数据的方法封装在一起,形成 ”对象“,并通过对象之间的交互和信息传递来完成程序的功能。面向对象编程强调数据的封装、继承、多态和动态绑定等特性,使得程序具有更好的扩展性、可维护性和可重用性。
以上是 ”面向对象” 的概念简介,如果读者有点懵也不要紧,在后面的实操中相信你能 Get 到那个点的,然后笔者这里只是做个简介,详细学习可以参考下面这个链接:
PHP 面向对象 | 菜鸟教程PHP 面向对象 面向对象(Object-Oriented,简称 OO)是一种编程思想和方法,它将程序中的数据和操作数据的方法封装在一起,形成“对象”,并通过对象之间的交互和消息传递来完成程序的功能。面向对象编程强调数据的封装、继承、多态和动态绑定等特性,使得程序具有更好的可扩展性、可维护性和可重用性。 在面向对象的程序设计(英语:Object-oriented programming,缩写:OOP)中,对象是一个由信息及对信息进行处..
https://www.runoob.com/php/php-oop.html
0x02:PHP 面向对象编程 — 知识速览
0x0201:PHP 面向对象编程 — 类的定义
在 PHP 中定义类需要注意以下几点:
-
类使用
class
关键字后加上类名进行定义。 -
类名后的一对大括号
{}
内可以定义变量与方法。 -
类中的变量使用
var
来声明,变量也可以进行初始化值。 -
函数定义类似 PHP 函数的定义,但类内部的函数只能通过该类及其实例化对象访问。
如下就是一个 PHP 类的示例:
// className 是类名class className {// 这个是类的成员变量var $var1 = "This Is a Class"; // 这个是类的成员函数function exampleFunction() {echo "This Is ExampleFunction !!\n";// 可以在类的成员函数中调用类的成员变量,但是得通过 $this 进行调用echo $this -> var1;}}
0x0202:PHP 面向对象编程 — 实例化类
类创建好后,我们就要使用它,使用类就需要对类进行 “实例化”,“实例化” 可以通过 new
关键字来进行实现,比如下面这个例子,我们实例化了 className
类,并且调用了其内部的成员方法:
<?php// className 是类名class className {// 这个是类的成员变量var $var1 = "This Is a Class"; // 这个是类的成员函数function exampleFunction() {echo "This Is ExampleFunction !!\n";// 可以在类的成员函数中调用类的成员变量,但是得通过 $this 进行调用echo $this -> var1;}}$class = new className(); // 实例化 className 类$class ->exampleFunction(); // 调用 className 类中的 exampleFunction() 方法(调用成员变量方法是一样的)
0x0203:PHP 面向对象编程 — 构造函数
构造函数是一种特殊的方法。主要用来在创建对象时初始化对象,即为对象成员变量赋初始值。
PHP5 允许开发者在一个类中定义一个方法作为构造函数,语法格式如下:
void __construct ([ mixed $args [, $...]] )
如下是一个示例,我们创建了一个 Animal 类,并利用构造函数在实例化每个对象(类)时赋予了它们自己的物种名称:
<?php// 动物类class Animal{// 这个保存物种类别var $animalType;// 这个就是构造函数function __construct($animalType) {$this -> animalType = $animalType;}// 当用户调用此函数,会输出当前动物的类型function getAnimalType() {echo "我是一只" . $this -> animalType . "\n";}}$dog = new Animal("小狗");$dog -> getAnimalType();$cat = new Animal("小猫");$cat -> getAnimalType();
0x0204:PHP 面向对象编程 — 析构函数
析构函数(destructor)与构造函数相反,当对象结束其生命周期时(例如对象所在的函数已调用完毕),系统就会自动执行该函数。
PHP 5 中引入了析构函数的概念,其基础语法如下:
void __destruct (void)
如下就是一个示例,当对象调用结束后,系统会自动销毁该对象并自动执行析构函数:
<?php// 动物类class Animal{// 这个保存物种类别var $animalType;// 这个就是构造函数function __construct($animalType) {$this -> animalType = $animalType;echo "你创造了一只" . $animalType . " !!!!\n";}// 当用户调用此函数,会输出当前动物的类型function getAnimalType() {echo "你好,我是一只" . $this -> animalType . "\n";}// 当实例化的对象结束其生命周期时就会执行我function __destruct(){echo "很遗憾," . $this -> animalType . "的生命周期结束了!!!"; // 有点悲惨是咋回事}}echo "=========== 创建一只小狗 ===========\n";$dog = new Animal("小狗");$dog -> getAnimalType();echo "=========== 很久很久以后 ===========\n";
0x0205:PHP 面向对象编程 — 类的继承
在 PHP 中可以通过关键字 extends
来继承一个类(但并不支持同时继承多个类),格式如下:
class Child extends Parent {// 类内部的代码}
比如下面这个示例,我们创建了一个 Dog
类继承自 Animal
类(Dog 类可以调用 Animal 类中已有的方法),并且扩展了功能(Dog 类可以拥有自己独特的方法):
<?php// 动物类
class Animal {// 这个保存物种类别var $animalType;// 这个就是构造函数function __construct($animalType) {$this -> animalType = $animalType;echo "你创造了一只" . $animalType . " !!!!\n";}// 当用户调用此函数,会输出当前动物的类型function getAnimalType() {echo "你好,我是一只" . $this -> animalType . "\n";}// 当实例化的对象结束其生命周期时就会执行我function __destruct() {echo "很遗憾," . $this -> animalType . "的生命周期结束了!!!"; // 有点悲惨是咋回事}
}class Dog extends Animal{// 调用后输出狗叫声function shout() {echo "汪汪 !!!\n";}
}echo "=========== 创建一只小狗 ===========\n";
$dog = new Dog("小狗");
$dog -> getAnimalType();
$dog -> shout();
echo "=========== 很久很久以后 ===========\n";
0x0206:PHP 面向对象编程 — 方法重写
如果从父类继承的方法不能满足子类的需求,子类可以对其进行改写,覆盖父类中的方法,这也被称为方法的重写。
比如下面这个例子,Animal 类中有一个 shout
方法,但是子类 Dog
觉得 Animal 中的 shout
不符合自己的意思,所以在自己的类中重写了该方法:
<?php// 动物类
class Animal {function shout() {echo "动物会叫 !!! \n";}
}class Dog extends Animal{// 调用后输出狗叫声function shout() {echo "汪汪 !!!\n";}
}$animal = new Animal();
$animal -> shout();$dog = new Dog();
$dog -> shout();
0x0207:PHP 面向对象编程 — 访问控制
PHP 对属性或方法的访问控制是通过在前面添加关键字 public(公有),protected(受保护)或 private(私有)来实现的:
-
public(公有): 共有的类成员可以在任何地方被访问。
-
protected(受保护): 受保护的类成员则可以被其自身以及其子类和父类访问。
-
private(私有): 私有的类成员只能被其定义所在的累访问。
类属性必须被定义为公有,受保护,私有之一。如果用 var
定义,则被视为公有。
类中的方法可以被定义为公有、私有或受保护。如果没有这些关键字,则该方法默认为公有。
下面这个示例演示的是在子类中可以访问父类中被 protected 修饰的变量,但是无法访问 private 修饰的变量(对于方法原理是一致的,笔者这里就不演示了):
<?php// 动物类
class Animal {// 定义一个私有属性 animalType,该属性只能在 Animal 类中使用private $animalType;protected $animalName;function __construct($animalType, $animalName) {$this -> animalType = $animalType;$this -> animalName = $animalName;}
}class Dog extends Animal{function getAnimalType() {echo $this -> animalType;}function getAnimalName() {echo $this -> animalName;}
}$animal = new Dog("🐕", "旺财\n");
$animal -> getAnimalName();
$animal -> getAnimalType();
0x0208:PHP 面向对象编程 — 接口(interface)
使用接口(interface),可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体内容。
接口是通过 interface
关键字定义的,就像定义一个标准类一样,但其中定义的所有方法都是空的。
接口中定义的所有方法都必须是公有的,这是接口的特性。
要实现一个接口,需要使用 implements
操作符。类中必须实现接口中定义的方法,否则就会被报错。一个类中可以实现多个接口,用逗号来分隔多个接口的名称:
<?php// 声明一个 Animal 接口
interface Animal {public function setType($type); // 设置动物类型public function setAge($age); // 设置动物年龄
}// 实现动物接口
class Dog implements Animal {var $type; // 动物类型var $age; // 动物年龄function __construct($type, $age){$this -> setType($type); // 调用内部函数设置动物类型$this -> setAge($age); // 调用内部函数设置动物年龄}// 实现接口方法,这个必须得有,不然报错public function setType($type) {$this -> type = $type;}// 实现接口方法,这个必须也得有,不然也给你报错public function setAge($age) {$this -> age = $age;}
}$dog = new Dog("狗", 18);
echo $dog -> type . " " . $dog -> age; // 输出: 狗 18
0x0209:PHP 面向对象编程 — 抽象类(Abstract)
任何一个类,如果它里面至少有一个方法是被声明为抽象的,那么这个类就必须被声明为抽象的。
抽象类是无法被实例化的。
被定义为抽象的方法只是声明了其调用方式(参数),不能定义具体的功能实现。
继承一个抽象类的时,子类必须定义父类中的所有抽象方法;另外,这些方法的访问控制必须和父类中的一样(或者更为宽松)。例如,某个抽象方法被声明为受保护的,那么子类中实现的方法就应该声明为受保护的或者公有的,而不能定义为私有的。
如下是一个例子,我们抽象了一个 Animal 类,我们假设每个动物都会叫,但叫声具体是啥我们不知道,所以我们抽象了一个 shout
方法。而继承自 Animal 类的 Dog 类和 Cat 类都要实现这个方法:
<?phpabstract class Animal {abstract public function shout(); // 动物的叫声// 抽象类中的普通方法public function whoami() {echo "我是一个动物!!\n";}
}class Dog extends Animal {// Dog 类需要实现抽象类中的 shout 方法public function shout() {echo "旺旺 !! ";}
}class Cat extends Animal {// Cat 类也需要实现抽象类中的 shout 方法public function shout() {echo "喵喵 !! ";}
}$dog = new Dog();
$dog -> shout();
$dog -> whoami(); // 调用 Dog 父类的方法$cat = new Cat();
$cat -> shout();
$cat -> whoami(); // 调用 Cat 父类的方法
此外,子类方法可以包含父类方法中不存在的可选参数(例子笔者就不写了,可以去开头介绍的那个文章那里学习)。
0x0210:PHP 面向对象编程 — 调用父类构造方法
当子类中有自己的构造方法时,PHP 不会自动调用父类的构造方法,要想执行父类的构造方法,就需要在子类的构造方法中添加下面这样语句(笔者懒,就不演示了):
parent::__construct()