PHP面向对象编程

1.类的定义:

  • 定义了一件事物的抽象特点
  • 类的定义包含了数据形式以及对数据的操作

1.2.类的定义与调用方法:

类的定义:

1
2
3
4
5
6
7
8
class 类名{
public $成员属性1=值;
public $成员属性2=值2;
public $成员属性3=值3;
public fuction 成员方法(){
echo "hello world!"
}
}

举例:

1
2
3
4
5
6
7
class Animal {
public $name="cat";
public $name2="dog";
public function eat(){
echo "在吃饭";
}
}

通过new关键字实例化,可以理解对象是一个灵魂,new实例化是给灵魂塑造肉身:

1
2
3
4
5
6
7
8
class Animal {
public $name="cat";
public $name2="dog";
public function eat(){
echo "在吃饭";
}
}
$animal=new Animal;

接下来我们要输出Animal中的第一个成员属性,即name1,可以使用 $实例化的变量->成员属性名来直接调用,不需要额外添加”$”符号

1
2
3
4
5
6
7
8
9
class Animal {
public $name="cat";
public $name2="dog";
public function eat(){
echo "在吃饭";
}
}
$animal=new Animal; //实例化Animal
echo $animal->name; //输出cat

调用成员方法也是一样的道理:

1
2
3
4
5
6
7
8
9
class Animal {
public $name="cat";
public $name2="dog";
public function eat(){
echo "在吃饭";
}
}
$animal=new Animal; //实例化Animal
echo $animal->eat(); //输出"在吃饭"

稍微复杂一点的拼接:

1
2
3
4
5
6
7
8
9
class Animal {
public $name="cat";
public $name2="dog";
public function eat($name){
echo $name."在吃饭";
}
}
$animal=new Animal; //实例化Animal
echo $animal->eat("小狗"); //输出"在吃饭"

1.3.$this关键字的介绍

this关键字用于指向类自己本身的属性或者方法

比如:

1
2
3
4
5
6
7
8
9
class Animal {
public $name="cat";
public $name2="dog";
public function eat(){
echo $this->name."在吃饭";
}
}
$animal=new Animal; //实例化Animal
echo $animal->eat(); //输出"cat在吃饭"

又:

1
2
3
4
5
6
7
8
9
10
11
class Animal {
public $name="cat";
public $name2="dog";
public function eat(){
echo $this->name."在吃饭";
}
}
$animal=new Animal; //实例化Animal
echo $animal->eat(); //输出"cat在吃饭"
$animal->name="pig";
echo $animal->eat(); //输出cat在吃饭,pig在吃饭

2.类的访问权限控制

1.public (共有) 公有的类成员可以在任何地方被访问

2.protected(受保护的)受保护的类成员则可以被其自身以及其子类和父类访问(咱家的)

1
2
3
4
5
6
7
8
9
10
11
12
<?php
class car {
protected $car1 = "benchi";
public $car2 = "qirui";
}
$car = new car();
echo $car->car1;
?>
PHP Fatal error: Uncaught Error: Cannot access protected property car::$car1 in /tmp/sandbox.s0-s0;c473,c741/home/.code.tio:7
Stack trace:
#0 {main}
thrown in /tmp/sandbox.s0-s0;c473,c741/home/.code.tio on line 7

可以看到调用触发保护机制,受保护的类无法被外部调用

3.private(私有) 私有的类成员则只能被其定义所在的类访问(自己的)

私有的

1
2
3
4
5
6
7
8
<?php
class car {
private $car1 = "benchi";
public $car2 = "qirui";
}
$car = new car();
echo $car->car1;
?>

private和protected如果想在外部调用私有类,可通过创建public 关键字声明的函数使用:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
class car {
private $car1 = "benchi";
public $car2 = "qirui";
public function name(){
return $this->car1;
}
}
$car = new car();
echo $car->name();
?>
//输出benchi

3.常用的魔术方法

3.1 构造函数(__construct )

介绍:

  • 构造函数是一种特殊的方法,在创建一个新对象的时候它会被自动调用
  • 可用来初始化对象的属性或执行其它必要操作,没有返回值

例子:

1
2
3
4
5
6
7
8
9
10
11
<?php
class fruit{
public $f1="apple";
public $f2="orange";
public function __construct(){
echo "I LIKE". " ".$this->f1;
}

}
$a=new fruit;
?> //I LIKE apple //注意:__construct 如果想要使用类中变量就要使用this来指向,否则会报错局部变量未定义

例2:

1
2
3
4
5
6
7
8
9
10
<?php
class fruit{
public $name;
public function __construct($name){
echo "I LIKE ".$name;
}
}

$a=new fruit("apple");
?> //I LIKE apple

可通过创建对象的时候赋值name,这种写法在CTF反序列化题目中非常常见

3.2析构函数(__destruct)

  • 析构函数是一种特殊的方法,他在对象被销毁时自动调用;
  • 它可以用来执行一些清理操作,例如释放资源或者关闭数据库连接
  • 当对象不再被引用或者脚本执行结束的时候,析构函数会被自动调用

例1:

1
2
3
4
5
6
7
8
9
<?php 
class obj {
public function __destruct(){
echo "im deleted!";
}
}
$obj=new obj;
unset($obj);
?>

使用unset触发__destruct ,输出”im deleted!”

例2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php 
class obj {
public function __destruct(){
echo "im deleted!";
}
}
$obj=new obj;
unset($obj);
if ($obj){
echo "im not delete";
}
else{
echo "im deleted!";
}
?>

检索对象是否存在,如果存在返回 im not delete 否则输出两遍im deleted! 并报错对象不存在

4.php的static静态变量和self关键字

静态变量的特点是无需实例化即可调用,通过static对其进行声明,可以通过类::变量的方式调用,在类的内部无法使用this调用,需要使用self对其代替

例1:外部调用静态变量

1
2
3
4
5
6
7
<?php 
class Animal{
public $a1="cat";
public static $a2="dog";
}
echo Animal::$a2;
?>

通过对象::变量来调用,输出dog,通过代码可以观察到省略了实例化的那一步

例2:内部调用静态变量:

1
2
3
4
5
6
7
8
9
10
11
<?php 
class Animal{
public $a1="cat";
public static $a2="dog";
public function say(){
echo self::$a2;
}
}
$a=new Animal;
$a->say();
?>

输出dog

在外部也可以对静态变量进行修改:

1
2
3
4
5
6
7
8
<?php 
class Animal{
public $a1="cat";
public static $a2="dog";
}
Animal::$a2="cow";
echo Animal::$a2;
?>

对象::静态变量名=”值”;

4.1类常量

这个知识点和静态变量非常类似,区别就是类常量不能随意更改,静态变量可以更改,类常量使用const进行声明,类中self进行调用

例1:

1
2
3
4
5
6
7
8
9
10
11
<?php 
class Animal{
public $a1="cat";
const a2="dog";
public function say(){
echo self::a2;
}
}
$a=new Animal;
$a->say();
?> //输出dog

常量不能加public等这些关键字,不加$符号

例2:外部调用

1
2
3
4
5
6
7
8
9
<?php 
class Animal{
public $a1="cat";
const a2="dog";
}
$obj=new Animal;
echo $obj::a2;

?> //输出dog

4.2静态方法

有了静态变量就有对应的静态方法,同样通过static关键字声明

1
2
3
4
5
6
7
8
9
10
<?php 
class cat {
public static $c1="狸猫";
public $c2="花猫";
public static function show(){
echo self::$c1;
}
}
cat::show();
?>

输出狸猫

4.3在类中调用非静态变量

1
2
3
4
5
6
7
8
9
10
<?php 
class cat {
public static $c1="狸猫";
public $c2="花猫";
public static function show(){
echo (new self)->c2;
}
}
cat::show();
?>

在类中通过 (new self) 将类本身实例化 再通过箭头指向非静态变量c2

5.类的继承

通过exends关键字继承父类的成员属性和方法,并且可以增删查改新的类

例1:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php 
class Animal {
public $a1="dog";
private $a2="cat";
protected $a3="monkey";
}

class Animal2 extends Animal{};
$a1=new Animal;
$a2=new Animal2;
var_dump($a1);
var_dump($a2);
?>

在这个例子中我们通过extends创建了一个类:Animal2 , 该类继承了父类Animal的所有属性,但是继承不一定可访问

上面的结果输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
object(Animal)#1 (3) {
["a1"]=>
string(3) "dog"
["a2":"Animal":private]=>
string(3) "cat"
["a3":protected]=>
string(6) "monkey"
}
object(Animal2)#2 (3) {
["a1"]=>
string(3) "dog"
["a2":"Animal":private]=>
string(3) "cat"
["a3":protected]=>
string(6) "monkey"
}

我们可以观察到Animal2 该类继承了父类Animal的所有属性

访问:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php 
class Animal {
public $a1="dog";
private $a2="cat";
protected $a3="monkey";
}

class Animal2 extends Animal{};
$an=new Animal;
$an2=new Animal2;
echo $an2->a1;
echo $an2->a2;
echo $an2->a3;
?>

输出dog,然后报错

因为a1的变量是public关键字声明的,可进行公共调用访问,a2是private私有的,只能在类中被调用,而protected只能在被继承的类中调用,不允许在外部被调用

那么,我们怎么能访问到protected 受保护的成员属性呢?

例2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php 
class Animal {
public $a1="dog";
private $a2="cat";
protected $a3="monkey";
}

class Animal2 extends Animal{
public function say () {
echo $this->a3;
}
};
$an=new Animal;
$an2=new Animal2;
echo $an2->say();
?>

在继承的类中,使用this指向a3即可调用

5.1魔术方法也可被继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php 
class Animal {
protected $name;
public function __construct($name){
$this->name=$name;
}
public function eat(){
echo $this->name."在吃饭";
}
}
class Animal2 extends Animal{
public function say() {
echo "hello,".$this->name;
}
}
$a=new Animal2("小猫");
$a->say();
echo " ";
$a->eat();

先通过继承构造函数赋值,在将赋值后的变量带到方法中,在这个例子中我们知道了魔术方法也可以被继承

fina

6.类方法属性重写和final关键字

如果父类继承的方法或者属性不能满足子类的需求,可以对其进行改写。

final关键字:

  • 防止类被继承
  • 防止类的方法被重写

先看一个类方法重写的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
class Animal {
public function makeSound() {
echo "Animal sound";
}
}

class Dog extends Animal {
// 重写父类的 makeSound 方法
public function makeSound() {
echo "Bark";
}
}

$dog = new Dog();
$dog->makeSound(); // 输出 "Bark"
$animal=new Animal();
$animal->makeSound();//输出Animal sound
?>

父类Animal中,存在方法makeSound ,子类Dog继承了Aniaml 的方法,在子类中可重新声明父类中的方法并进行改写,而父类的方法不受任何影响:上面的代码运行结果为:BarkAnimal sound

在开发中,有时父类方法是强制性的,不能被改写,需要在publIc声明中添加关键字final关键字来告诉 方法不能被改写

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
class Animal {
final public function makeSound() {
echo "Animal sound";
}
}

class Dog extends Animal {
// 重写父类的 makeSound 方法
public function makeSound() {
echo "Bark";
}
}

$dog = new Dog();
$dog->makeSound(); // 输出 "Bark"
$animal=new Animal();
$animal->makeSound();//输出Animal sound
?>

在代码中我们在父类的方法中添加了final关键字,这时子类继承父类方法时,会报错:

1
PHP Fatal error:  Cannot override final method Animal::makeSound() in /tmp/sandbox.s0-s0;c344,c899/home/.code.tio on line 13

注:final关键字不能加在成员属性前,只能用于成员方法

7.使用parent调用父类方法和构造函数

我们通过parent关键字调用父类的方法,但是前提是需要将parent::method 放在子类的方法中,具体示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php 
class Animal {
public function method1 () {
echo "hello world!";
}
}
class Dog extends Animal {
public function mothod2(){
parent::method1();
}
}
$a=new Animal;
$b=new Dog;
$b->method1();
?>

如上,我们在子类Dog嵌套了一个方法:method2() 我们要想使用父类的method1 需要在子类的方法中调用

7.1 使用parent 调用构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
class Animal {

public $name;
public function __construct($name){
$this->name=$name;
echo "hello".$name;
}
}
class cat extends Animal {
public function __construct ($name) {
parent::__construct($name);
echo "你好".$name;
}
}

$cat = new cat("世界"); //输出hello 世界你好世界
?>

在这个例子中,我们依旧定义了两个类Animal和cat 父类使用了construct方法,在new 的时候对name进行了赋值,并输出hello 拼接name

子类中 也使用了construct方法,并继承了父类的成员属性$name ,接下来在子类构造方法的函数下面又调用了父类的构造函数,并输出你好拼接变量name

最终实例化子类的对象,赋值参数字符串”世界” 输出hello 世界你好世界

这块的逻辑可能不是很好理解,需多加练习

7.2静态延迟绑定:static

先看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class Animal {
protected static $name = "动物";

public static function who() {
echo self::$name; // 编译时绑定:始终输出 Animal 类的 $name
}
}
class Cat extends Animal {
protected static $name = "猫";
}
Cat::who();
?>

输出动物而不是猫,因为self 永远指向类本身,在例子中代码编译时就把name给绑死了,无法动态绑定,为了解决这种问题我们需要加上static关键字 将其动态绑定

加了static之后:

1
2
3
4
5
6
7
8
9
10
11
class Animal {
protected static $name = "动物";

public static function who() {
echo static::$name;
}
}
class Cat extends Animal {
protected static $name = "猫";
}
Cat::who(); //输出猫

加了之后变量被覆写时也不会强制绑定原来的那个类

8.类的多态以及方法重载

什么是类的多态?

多态(Polymorphism)面向对象编程核心特性之一,指同一个接口(方法名)在不同类中具有不同实现。运行时根据对象实际类型调用对应方法 例如之前的例子:子类覆写父类的成员方法 但是多态不仅仅只有这些 ,在这里我们仅做一个简单的概念

重载的介绍

PHP中的重载是通过魔术方法动态处理未定义或不可访问的属性和方法,而非传统意义上的同名不同参数方法重载。

重载函数:

1.func_get_args();

2.func_num_args();

func_get_args() 用于获取参数,而func_num_args(); 则用于获取参数的数量

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class Animal{

public function say(){
$a=func_get_args();
$b=func_num_args();
var_dump($a);
var_dump($b);
}

}
$n=new Animal;
$n->say();

执行结果:

1
2
3
array(0) {
}
int(0)

因为我们没有向say()传任何参数

如果是:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class Animal{

public function say(){
$a=func_get_args();
$b=func_num_args();
var_dump($a);
var_dump($b);
}

}
$n=new Animal;
$n->say(1,2,3);

则返回:

1
2
3
4
5
6
7
8
9
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
}
int(3)

可以看到把每个参数以及参数的数量都给回显了

案例:根据不同的参数数量执行不同的事件:

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
<?php
class Animal{

public function say(){
$a=func_get_args();
$b=func_num_args();
var_dump($a);
var_dump($b);
if ($b==1){
echo "触发第一次事件";


}
elseif ($b==2) {
echo "触发第二次事件";
}
elseif($b==3) {
echo "触发第三次事件";
}
else {
echo "触发默认事件";
}

}

}
$n=new Animal;
$n->say(1);
?>

9.interface接口规范

interface接口指的是一组方法的集合,不是类,不能被实例化

指定某些类必须实现某些方法,但是具体内容没有规定

只能使用public 声明

用于定义一些规范,使代码不容易出错

语法:

1
2
3
4
5
interface 接口名称  {
public func1();
public func2();
....
}

配合class类进行调用接口规范:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php 
interface A {
public function func1();
public function func2();
public function func3();
}

class AImplementation implements A {
public function func1() { echo 1; }
public function func2() { echo 2; }
public function func3() { echo 3; }
}
$a=new AImplementation();
$a->func1();
$a->func2();
$a->func3();
?>


AImplementation 类的成员方法名称必须要完全符合interface 内定义的接口规范名称

interface内还可以用于定义静态方法,以及自定义函数中的参数名称

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php 
interface A {
public static function func1($name);
public function func2();
public function func3();
}

class AImplementation implements A {
public static function func1($name) { echo $name; }
public function func2() { echo 2; }
public function func3() { echo 3; }
}
$a=new AImplementation();
$a::func1("hello world");
$a->func2();
$a->func3();
?>

也可以同时实现两个接口 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php 
interface A {
public static function func1($name);
}
interface B {
public function func2();
public function func3();
}

class AImplementation implements A,B {
public static function func1($name) { echo $name; }
public function func2() { echo 2; }
public function func3() { echo 3; }
}
$a=new AImplementation();
$a::func1("hello world");
$a->func2();
$a->func3();
?>

中间使用,将其隔开 通过implements 实现两个接口 这两个接口里面的规范都必须被符合

10.抽象类和抽象方法

  • 抽象类是一种特殊的类,不能被实例化只能被继承
  • 抽象类用于定义一组相关的方法,但是这些方法的具体实现由继承它的子类完成
  • 子类继承抽象类后,必须实现抽象类的所有抽象方法
  • 抽象类可以包含抽象方法和普通方法

抽象类和Interface有共同之处,也是定义了一组规范 使用abstract关键字做声明

抽象方法只存在于抽象类中 可以使用protected 但是不能使用private

语法:

abstract class Animal {

​ abstract public 函数($变量);

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php

abstract class Animal {

abstract public function eat($food);
protected $a1="cat";
}
class Animal1 extends Animal {
public $b1="cat";
public $a2="dog";
public function eat ($food) {
echo "eat"." "."$food";
echo "\n";
echo $this->a1;
}
}
$a=new Animal1;
$a->eat("patato");

输出:

1
2
eat patato
cat

抽象类可以理解成一个上层语法概念,使用abstract public function eat($food); 约束子类的方法规则 和interface一样可以不用实现具体方法代码 在抽象类定义的变量也可以被子类继承

例2:抽象类也可写入__contruct 方法

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
<?php

abstract class Animal {
abstract public function eat($food);
protected $a1 = "cat";
public $time;


public function sleep() {
echo "睡了".$this->time."秒"; // 使用对象属性
}

public function __construct($time) {
$this->time = $time;
}

}

class Animal1 extends Animal {
public $b1 = "cat";
public $a2 = "dog";


public function eat($food) {
echo "eat "."$food";
echo "\n";
echo $this->a1;
}

}

$a = new Animal1("3");
$a->sleep(); // 输出:睡了3秒
$a->eat("patato"); // 输出:eat potato\ncat
?>

与interface的区别:

interface是一种规范 而abstract 是一个类 相比interface更加灵活

11.trait关键字的复用

  • 解决类的单一继承问题
  • 可以使用多个trait 进行复用 中间使用逗号隔开
  • 把常用的,通用的代码写成trait
  • trait 可以使用抽象方法
  • trait可以使用其他trait
  • trait中可以使用parent

语法:

1
2
3
4
5
6
7
8
9
trait A {

}
trait B {

}
class C {
use A,B....
}

trait和类的继承非常的像 但是trait中不能有类常量,且trait不能被实例化

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
trait Animal {
public $a1 = "dog";
abstract function say();
}
class Animal2 {
use Animal;
public function say(){
echo 1;
}
}
$a=new Animal2();
var_dump($a);
?>

运行结果:

1
2
3
4
object(Animal2)#1 (1) {
["a1"]=>
string(3) "dog"
}

可以看到成功继承了 父类Animal的内容

例2:代码复用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
trait A {
public function test() {
echo 1;
}
}
trait B {
public function test() {
echo 2;
}
}
class Test {
use A; // 使用 trait A
}
$T = new Test();
$T->test(); // 正确调用方法
?>

在这个例子中 我们将代码放入两个类中 引用的时候用use 来代码复用

输出1

使用trait 调用其他trait:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
trait A {
public function test() {
echo 1;
}
}
trait B {
use A;
public function test2() {
echo 2;
}
}
class Test {
use B; // 使用 trait A
}
$T = new Test();
$T->test(); // 正确调用方法
?>

在这个例子中 我们在B中引用了A 的test函数 所以实例化之后的Test实际上调用的是A的test函数

11.1在trait 中调用关键字parent::指向父类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
class ParentClass {
public function foo() { echo "Parent\n"; }
}

trait MyTrait {
public function bar() {
parent::foo();
echo "Trait\n";
}
}

class Child extends ParentClass {
use MyTrait;
}

$obj = new Child();
$obj->bar();
?>

class引用了 Mytrait 里面的属性 也把parent同样给带下来了

最终变成了Child 继承ParentClass 的方法