PHP语法高级篇(六):面向对象编程
在PHP开发中,面向对象编程是构建可维护、可扩展系统的核心支柱。它通过封装业务逻辑、实现多态复用,大幅提升代码组织性与模块化程度——这对快速迭代的小型项目尤为重要。作为具备Java经验的开发者,早已理解了封装、继承、多态的价值。所以,本篇文章不讨论OOP理论,只聚焦一个目标:用最短时间掌握PHP实现面向对象的语法。
目录
一、类和对象
1、定义类
2、定义对象
3、instanceof
二、构造函数和析构函数
1、构造函数
2、析构函数
三、访问控制(可见性)
四、继承
1、重写继承的方法
2、final 关键字
五、类常量
六、抽象类
七、接口
八、Trait
九、静态(static)关键字
1、静态方法
2、静态属性
十、命名空间
1、定义命名空间
2、定义子命名空间
3、使用命名空间
4、命名空间别名
一、类和对象
1、定义类
每个类的定义都以关键字 class 开头,后面跟着类名,后面跟着一对花括号({}),里面包含有类的属性与方法的定义。类名可以是任何非 PHP 保留字的合法标签。一个合法类名以字母或下划线开头,后面跟着若干字母,数字或下划线。一个类可以包含有属于自己的 常量、变量(称为"属性")以及函数(称为"方法")。
示例 简单的类定义
<?php
class SimpleClass {// 声明属性public $var = 'a default value';// 声明方法public function displayVar() {echo $this->var;}
}
?>
当一个方法在类定义内部被调用时,有一个可用的伪变量 $this。$this 是一个到当前对象的引用。
2、定义对象
要创建一个类的实例,必须使用 new 关键字。如果一个变量包含一个类名的 string 和 new 时,将创建该类的一个新实例。如果该类属于一个命名空间(命令空间的内容将在本文后面进行说明),则必须使用其完整名称。
示例 创建实例
<?php
$instance = new SimpleClass();// 也可以这样做:
$className = 'SimpleClass';
$instance = new $className(); // 等同于:new SimpleClass()
?>
PHP 8.0.0 起,支持任意表达式中使用 new。如果表达式生成一个 string,这将允许更复杂的实例化。表达式必须使用括号括起来。
3、instanceof
可以使用 instanceof 关键字来检查一个对象是否属于特定的类。
示例
<?php
$instance = new SimpleClass();
echo var_dump($instance instanceof SimpleClass);
?>
二、构造函数和析构函数
1、构造函数
PHP 允许在一个类中定义一个方法作为构造函数。具有构造函数的类会在每次创建新对象时先调用此方法,所以非常适合在使用对象之前做一些初始化工作。
__construct(mixed ...$values = ""): void
注意:构造函数以两个下划线(__)开头!
如果子类中定义了构造函数则不会隐式调用其父类的构造函数。要执行父类的构造函数,需要在子类的构造函数中调用 parent::__construct()。如果子类没有定义构造函数则会如同一个普通的类方法一样从父类继承(假如没有被定义为 private 的话)。
示例
<?php
class Sutdent {public $name;public $age;function __construct($name, $age) {$this->name = $name;$this->age = $age;}function display() {echo "姓名:" . $this->name . " ,年龄:" . $this->age . " <br>";}
}$stu = new Sutdent("zhangsan", 16);
$stu->display();
?>
2、析构函数
析构函数会在对象的所有引用都被删除或者当对象被显式销毁时执行。
__destruct(): void
注意:析构函数以两个下划线(__)开头!
我们现在在刚才构造函数的示例中再加入一个析构函数,它会在脚本结束时自动调用。
<?php
class Sutdent {public $name;public $age;function __construct($name, $age) {$this->name = $name;$this->age = $age;}function display() {echo "姓名:" . $this->name . " ,年龄:" . $this->age . " <br>";}function __destruct() {echo '对象被销毁';}
}$stu = new Sutdent("zhangsan", 16);
$stu->display();
?>
和构造函数一样,父类的析构函数不会被引擎暗中调用。要执行父类的析构函数,必须在子类的析构函数体中显式调用 parent::__destruct()。此外也和构造函数一样,子类如果自己没有定义析构函数则会继承父类的。
析构函数即使在使用 exit() 终止脚本运行时也会被调用。
三、访问控制(可见性)
对属性或方法的访问控制,是通过在前面添加关键字 public(公有),protected(受保护)或 private(私有)来实现的。
-
public - 属性或方法可以在任何地方被访问。这是默认值
-
protected - 属性或方法可以被其自身以及其子类和父类访问
-
private - 属性或方法只能在类内部被访问
示例 属性的访问控制
<?php
class MyClass {public $public = 'Public';protected $protected = 'Protected';private $private = 'Private';function printHello() {echo $this->public;echo $this->protected;echo $this->private;}
}$obj = new MyClass();
echo $obj->public; // 正常执行
echo $obj->protected; // 产生一个致命错误
echo $obj->private; // 产生一个致命错误
$obj->printHello(); // 输出 Public、Protected 和 Private
?>
示例 方法的访问控制
<?php
class MyClass {// 声明一个公有的构造函数public function __construct() { }// 声明一个公有的方法public function MyPublic() { }// 声明一个受保护的方法protected function MyProtected() { }// 声明一个私有的方法private function MyPrivate() { }
}$myclass = new MyClass();
$myclass->MyPublic(); // 正常执行
$myclass->MyProtected(); // 产生一个致命错误
$myclass->MyPrivate(); // 产生一个致命错误
?>
四、继承
继承是大家所熟知的一个程序设计特性,PHP 的对象模型也使用了继承。继承将会影响到类与类,对象与对象之间的关系。比如,当扩展一个类,子类就会继承父类所有 public 和 protected 的方法,属性和常量。除非子类覆盖了父类的方法,被继承的方法都会保留其原有功能。此外,它还可以有自己的属性和方法。
一个类可以在声明中用 extends 关键字继承另一个类的方法和属性。PHP 不支持多重继承,一个类只能继承一个基类。
除非使用了自动加载,否则一个类必须在使用之前被定义。如果一个类扩展了另一个,则父类必须在子类之前被声明。此规则适用于类继承其它类与接口。
示例
<?php
class Person {public $name;public $age;function __construct($name, $age) {$this->name = $name;$this->age = $age;}
}class Student extends Person {function display() {echo "姓名:$this->name , 年龄:$this->age <br>";}
}$stu = new Student("zhangsan", 18);
$stu->display();
?>
示例解释
Student 类继承自 Person 类。这意味着 Student 类可以使用 Person 类中的公共属性 $name 和 $age 以及公共方法 __construct() ,这是因为继承。Student 类也有自己的方法:display()。
1、重写继承的方法
被继承的方法和属性可以通过用同样的名字重新声明被覆盖。但是如果父类定义方法或者常量时使用了 final,则不可被覆盖。可以通过 parent:: 来访问被覆盖的方法或属性。
示例 重写方法
<?php
class SimpleClass {function display() {echo "父类的 display 方法";}
}class SubClass extends SimpleClass {// 同样名称的方法,将会覆盖父类的方法function display() {echo "子类的 display 方法";}
}$sub = new SubClass();
$sub->display();
?>
2、final 关键字
final 关键字通过在定义方法和常量之前加上 final 来防止被子类覆盖。如果一个类被声明为 final,则不能被继承。
示例
<?php
final class SimpleClass {// 一些代码
}// 将导致错误
class SubClass extends SimpleClass {// 一些代码
}
?>
五、类常量
可以把在类中始终保持不变的值定义为 常量 ,类常量的默认可见性是 public 。
类常量使用 const 关键字在类内部声明,常量一旦被声明就不能更改。类常量是区分大小写的。但是,建议使用全大写字母命名常量。
可以使用类名后跟作用域解析运算符 (::) 后跟常量名从类外部访问常量。
示例
<?php
class SimpleClass {const MESSAGE = '类常量';
}echo SimpleClass::MESSAGE;
?>
我们也可以使用 self 关键字后跟作用域解析运算符 (::) 后跟常量名从类内部访问常量。
<?php
class SimpleClass {const MESSAGE = '类常量';function display() {echo self::MESSAGE;}
}$simple = new SimpleClass();
$simple->display();
?>
六、抽象类
PHP 有抽象类和抽象方法。定义为抽象的类不能被实例化。任何一个类,如果它里面至少有一个方法是被声明为抽象的,那么这个类就必须被声明为抽象的。被定义为抽象的方法只是声明了其调用方式(参数),不能定义其具体的功能实现。
继承一个抽象类的时候,子类必须实现父类中的所有抽象方法。
抽象类或抽象方法使用 abstract 关键字来定义。
语法
<?php
abstract class ParentClass {abstract public function someMethod1();abstract public function someMethod2($name, $color);abstract public function someMethod3() : string;
}
?>
当从抽象类继承时,子类方法必须使用相同的名称,以及相同或更宽松的访问修饰符。因此,如果抽象方法被定义为 protected,子类方法必须定义为 protected 或 public,但不能是 private。此外,必需参数的类型和数量必须相同。但是,子类可以拥有额外的可选参数。
示例
<?php
abstract class AbstractClass {// 强制要求子类定义这些方法abstract protected function testA();abstract protected function testB($num1, $num2);// 普通方法(非抽象方法)public function printOut() {echo "普通方法<br>";}
}class SubClass extends AbstractClass {public function testA() {echo "实现父类抽象方法 testA()<br>";}public function testB($num1, $num2) {return $num1 + $num2;}
}$sub = new SubClass();
$sub->testA();
echo $sub->testB(5, 6) . "<br>";
$sub->printOut();
?>
以上示例会输出:
实现父类抽象方法 testA()
11
普通方法
七、接口
使用接口(interface),可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体内容。由于接口(interface)和类(class)、trait 共享了命名空间,所以它们不能重名。
接口就像定义一个标准的类一样,通过 interface 关键字替换掉 class 关键字来定义,但其中所有的方法都是空的。接口中定义的所有方法都必须是 public ,这是接口的特性。
语法
<?php
interface InterfaceName {public function someMethod1();public function someMethod2($name, $color);public function someMethod3() : string;
}
?>
接口也可以通过 extends 关键字扩展。
要实现一个接口,使用 implements 关键字。类中必须实现接口中定义的所有方法,否则会报一个致命错误。类可以实现多个接口,用逗号来分隔多个接口的名称。实现接口的类必须实现接口的所有方法。
示例
<?php
// 声明接口
interface Test {function print();
}// 定义类实现接口
class TestA implements Test {function print() {echo "实现类TestA<br>";}
}class TestB implements Test {function print() {echo "实现类TestB<br>";}
}$testA = new TestA();
$testA->print();$testB = new TestB();
$testB->print();
?>
八、Trait
PHP 仅支持单继承:一个子类只能从一个父类继承。那么,如果一个类需要继承多个行为怎么办?
PHP 实现了一种代码复用的方法,称为 trait。Trait 是为 PHP 的单继承语言而准备的一种代码复用机制。Trait 减少单继承语言的限制,使开发人员能够自由地在不同层次结构内的类中复用方法。Trait 可以有方法和抽象方法,这些方法可以在多个类中使用,并且这些方法可以有任何访问修饰符(public、private 或 protected)。
Trait 使用 trait 关键字声明。
语法
<?php
trait TraitName {// 代码...
}
?>
要在类中使用 trait,请使用 use 关键字:
<?php
class MyClass {use TraitName;
}
?>
示例
<?php
trait Display {function print() {echo "Trait中的方法<br>";}
}class Test {use Display;
}$test = new Test();
$test->print();
?>
在上面的示例中,我们声明了一个 trait:Display。然后,又创建了一个类:Test。该类使用了这个 trait,trait 中的所有方法都将在该类中可用。
通过逗号分隔,可以在 use 中使用多个 trait。
示例 多个 trait 的用法
<?php
trait Display {function print() {echo "Display中的方法<br>";}
}trait Message {function msg() {echo "Message中的方法";}
}class Test {use Display, Message;
}$test = new Test();
$test->print();
$test->msg();
?>
九、静态(static)关键字
声明类属性或方法为静态,就可以不实例化类而直接访问。
1、静态方法
静态方法使用 static 关键字声明。访问静态方法,需使用类名、双冒号 (::) 和方法名。
示例
<?php
class Test {static function print() {echo "静态方法<br>";}
}// 类名::方法名 方式调用静态方法
Test::print();
?>
一个类可以既有静态方法也有非静态方法。静态方法可以在同一类的其他方法中使用 self 关键字和双冒号 (::) 来访问:
<?php
class Test {static function print() {echo "静态方法<br>";}function printOut() {self::print();}
}$test = new Test();
$test->printOut();
?>
2、静态属性
静态属性使用 范围解析操作符( :: )访问,不能通过对象操作符( -> )访问。
静态属性使用 static 关键字声明。
示例
<?php
class Math {static $pi = 3.14;
}echo Math::$pi;
?>
和静态方法一样,一个类可以既有静态属性也有非静态属性。静态属性可以在同一类的其他方法中使用 self 关键字和双冒号 (::) 来访问。
十、命名空间
在 PHP 中,命名空间用来解决在编写类库或应用程序时创建可重用的代码如类或函数时碰到的两类问题:
- 用户编写的代码与PHP内部的类/函数/常量或第三方类/函数/常量之间的名字冲突。
- 为很长的标识符名称(通常是为了缓解第一类问题而定义的)创建一个别名(或简短)的名称,提高源代码的可读性。
PHP 命名空间提供了一种将相关的类、函数和常量组合到一起的途径。
1、定义命名空间
虽然任意合法的 PHP 代码都可以包含在命名空间中,但只有以下类型的代码受命名空间的影响,它们是:类(包括抽象类和 trait)、接口、函数和常量。
命名空间通过关键字 namespace 来声明。如果一个文件中包含命名空间,它必须在其它所有代码之前声明命名空间。
示例
<?php
namespace MyProject;
?>
注意:命名空间声明必须是 PHP 文件中的第一个语句。以下代码将会产生致命错误:
<html>
<?php
namespace MyProject;
?>
同一个命名空间可以定义在多个文件中,即允许将同一个命名空间的内容分割存放在不同的文件中。
2、定义子命名空间
与目录和文件的关系很象,PHP 命名空间也允许指定层次化的命名空间的名称。因此,命名空间的名字可以使用分层次的方式定义:
<?php
namespace MyProject\Sub;class Test {}
function printOut() {}
?>
上面的例子创建了类 MyProject\Sub\Test 和函数 MyProject\Sub\printOut。
3、使用命名空间
任何在命名空间声明之后的代码都在该命名空间中运行,因此属于命名空间的类可以不带任何限定符进行实例化。要访问命名空间外部的类,类名前面需要加上命名空间。
示例 使用 MyProject\Sub 命名空间下的 Test 类
<?php
include "test.php"; // 包含 MyProject\Sub\Test 类的 php 脚本文件$test = new MyProject\Sub\Test();
?>
4、命名空间别名
允许通过别名引用或导入外部的完全限定名称,是命名空间的一个重要特征。为命名空间或类指定别名可以使代码的编写更加方便。这是通过 use 关键字完成的。
PHP 可以为这些项目导入或设置别名:常量、函数、类、接口、trait、枚举和命名空间。
示例
<?php
include "test.php"; // 包含 MyProject\Sub\Test 类的 php 脚本文件// 为命名空间指定别名
use MyProject\Sub as Project;
$t = new Project\Test();// 为类指定别名
use MyProject\Sub\Test as T;
$t1 = new T();
?>