这里整理了一些 PHP 面向对象编程中容易搞混的知识点,很多都是面试常考题。不过学这些不只是为了应付面试,更重要的是真正搞懂面向对象编程的原理。
魔术方法就是那些以双下划线(__)开头的特殊方法,在特定情况下 PHP 会自动调用它们。
这些方法是 PHP 内置的,可以让你控制对象在各种情况下的行为。
常用的魔术方法:
__construct()
→ 创建对象时自动执行__destruct()
→ 对象销毁时自动执行__get($name)
→ 读取不存在的属性时调用__set($name, $value)
→ 设置不存在的属性时调用__isset($name)
→ 对不存在的属性用 isset()
或 empty()
时调用__unset($name)
→ 对不存在的属性用 unset()
时调用__call($method, $args)
→ 调用不存在的方法时触发__callStatic($method, $args)
→ 调用不存在的静态方法时触发__toString()
→ 对象转字符串时调用__invoke()
→ 把对象当函数用时调用__clone()
→ 克隆对象时执行PHP 中的构造函数是一个特殊方法(__construct
),在从类创建对象时自动执行。
__construct()
定义🗹 构造函数类型
1- 默认构造函数
如果你没有定义构造函数,系统会自动创建一个。
class A
{
}
$obj = new A(); // 即使没有 __construct 也能正常工作
2- 参数化构造函数
创建对象时接受参数。
class B
{
public function __construct($name)
{
echo "Hello $name";
}
}
new B("John"); // Hello John
3- 拷贝构造函数(PHP 中的模拟实现)
PHP 没有像 C++ 那样真正的拷贝构造函数,但你可以通过向构造函数传递对象来模拟。
class C
{
public $x;
public function __construct($obj = null) {
if ($obj) $this->x = $obj->x;
}
}
可以,在 PHP 中,构造函数可以是私有的。
为什么要这样做?
class Singleton {
private static $instance;
private function __construct() {
echo "私有构造函数\n";
}
public static function getInstance() {
if (!self::$instance) {
self::$instance = new Singleton();
}
return self::$instance;
}
}
$obj1 = Singleton::getInstance(); // 正常工作
$obj2 = new Singleton(); // ❌ 错误:无法访问私有构造函数
不可以,你无法直接从抽象类创建实例。必须用子类继承它,然后从子类创建对象。
abstract class Animal {
abstract public function sound();
}
// ❌ $a = new Animal(); // 错误
class Dog extends Animal {
public function sound() {
return "汪汪";
}
}
$d = new Dog(); // ✅ 正常工作
echo $d->sound(); // 汪汪
原因是抽象类是不完整的。
不可以,接口只是一个契约;你必须在类中实现它,然后从该类创建对象。
👉 简而言之:接口 = 契约,不是对象。
可以。PHP 允许接口继承多个接口(接口的多重继承)。
interface CanDrive {
public function drive();
}
interface CanFly {
public function fly();
}
interface FlyingCar extends CanDrive, CanFly {}
class MyFlyingCar implements FlyingCar {
public function drive() { echo "正在驾驶\n"; }
public function fly() { echo "正在飞行\n"; }
}
$car = new MyFlyingCar();
$car->drive(); // 正在驾驶
$car->fly(); // 正在飞行
在 PHP(从 PHP 8.0 开始),构造函数属性提升是一种简写方式,让你可以直接在构造函数中声明和初始化类属性。
这减少了样板代码。
🗹 之前(没有属性提升):
class User {
private string $name;
private int $age;
public function __construct(string $name, int $age) {
$this->name = $name;
$this->age = $age;
}
}
🗹 之后(使用属性提升):
class User {
public function __construct(
private string $name,
private int $age
) {}
}
PHP 拥有与其他面向对象语言(如 C++)类似的析构函数概念。一旦不再有对特定对象的其他引用,或者在关闭序列期间以任何顺序,析构函数方法就会被调用。
PHP 中的析构函数是一个名为 __destruct()
的特殊方法,当对象被销毁或超出作用域时自动执行。
🗹 要点:
class Test {
public function __construct() {
echo "对象已创建\n";
}
public function __destruct() {
echo "对象已销毁\n";
}
}
$t = new Test(); // 对象已创建
// 当脚本结束或 $t 被 unset 时 → 对象已销毁
👉 简而言之:__construct()
= 设置,__destruct()
= 清理。
在 PHP 中,final
关键字用于防止重写或继承。
Final 类 → 不能被继承。
final class Base {
public function sayHello() {
echo "Hello";
}
}
class Child extends Base {} // ❌ 错误:无法继承 final 类
Final 方法 → 不能在子类中被重写。
class Base {
final public function sayHello() {
echo "Hello";
}
}
class Child extends Base {
public function sayHello() {} // ❌ 错误:无法重写 final 方法
}
🗹 final
🗹 abstract
👉 记忆技巧:
PHP 中的静态方法是属于类本身而不是类实例的方法。
static
关键字声明ClassName::methodName()
调用,而不是 $object->methodName()
(因为 $this
需要对象)class Math {
public static function add($a, $b) {
return $a + $b;
}
}
echo Math::add(5, 3); // 8
不可以,在 PHP 中,如果在子类中重写方法,其静态/非静态性质必须与父类匹配。
class ParentClass {
public function show() {}
}
class ChildClass extends ParentClass {
public static function show() {} // ❌ 致命错误
}
// 致命错误:无法将非静态方法 ParentClass::show() 在类 ChildClass 中设为静态
🗹 self::
class A {
public static function who() { echo "A"; }
public static function test() { self::who(); }
}
class B extends A {
public static function who() { echo "B"; }
}
B::test(); // 输出 "A"
🗹 static::(后期静态绑定)
class A {
public static function who() { echo "A"; }
public static function test() { static::who(); }
}
class B extends A {
public static function who() { echo "B"; }
}
B::test(); // 输出 "B"
🗹 parent::
class A {
public function greet() { echo "来自 A 的问候"; }
}
class B extends A {
public function greet() {
parent::greet(); // 调用父类方法
echo " 以及来自 B 的问候";
}
}
$obj = new B();
$obj->greet(); // 来自 A 的问候 以及来自 B 的问候
👉 总结:
🗹 $this
class User {
public $name = "Ali";
public function getName() {
return $this->name; // 指向当前对象
}
}
$user = new User();
echo $user->getName(); // Ali
🗹 self
class User {
const ROLE = "会员";
public static function getRole() {
return self::ROLE; // 指向 User::ROLE
}
}
echo User::getRole(); // 会员
$this → 对象 → 实例上下文self → 类 → 静态上下文
PHP 中的后期静态绑定(LSB)是关于 static::
与 self::
的工作方式对比。
🗹 核心思想
self::
→ 在编译时解析(编写它的类)static::
→ 在运行时解析(实际调用的类)这就是为什么称为后期——PHP 只在调用方法时才决定使用哪个类。
👉 简而言之:
如果两个 trait 定义了相同的方法,PHP 会抛出致命错误,除非你明确解决冲突。
你可以在类内部使用 insteadof
或 as
来解决冲突。
trait A {
public function sayHello() { echo "来自 A 的问候"; }
}
trait B {
public function sayHello() { echo "来自 B 的问候"; }
}
class Test {
use A, B {
A::sayHello insteadof B; // 使用 A 的版本
B::sayHello as sayHelloB; // 给 B 的版本起别名
}
}
$obj = new Test();
$obj->sayHello(); // 来自 A 的问候
$obj->sayHelloB(); // 来自 B 的问候
👉 简而言之:
如果两个 trait 定义了相同的属性,PHP 会抛出致命错误,因为它无法决定使用哪一个。
示例:
trait A {
public $x = 1;
}
trait B {
public $x = 2; // 冲突
}
class Test {
use A, B; // ❌ 致命错误:Trait 属性冲突
}
👉 与方法不同(可以使用 insteadof
或 as
解决),属性冲突无法解决——你必须重命名或删除其中一个。
不可以。
implements
👉 简而言之:Trait 不能实现接口,只有类可以。
🗹 接口
🗹 抽象类
👉 何时使用:
🗹 继承("是一个"关系)
class Vehicle {
public function move()
{
echo "移动中";
}
}
class Car extends Vehicle {} // Car *是一个* Vehicle
$car = new Car();
$car->move(); // 移动中
🗹 组合("有一个"关系)
class Engine {
public function start()
{
echo "引擎启动";
}
}
class Car {
private $engine;
public function __construct()
{
$this->engine = new Engine();
}
public function drive() { $this->engine->start(); echo " & 正在驾驶"; }
}
$car = new Car();
$car->drive(); // 引擎启动 & 正在驾驶
⚖️ 何时使用
👉 经验法则:优先使用组合而不是继承(常见的设计原则)。
🗹 组合优于继承原则
组合优于继承原则建议尽可能使用组合而不是继承。原因是虽然继承允许你根据另一个类来定义一个类的行为,但它将父类的生命周期和访问级别绑定到子类,这可能使代码更加僵化。
组合允许对象通过关系而不是通过僵化的类结构来获得行为和特性,从而实现更好的模块化和类之间更低的耦合。
不可以,子类不能降低继承方法的可见性。 如果方法在父类中是 public,在子类中必须保持 public。 可见性只能扩大(例如,protected → public),但绝不能限制(public → protected/private)。
🗹 降低可见性
class ParentClass {
public function showMessage() {
echo "来自父类的消息";
}
}
class ChildClass extends ParentClass {
// ❌ 这会导致致命错误
protected function showMessage() {
echo "来自子类的消息";
}
}
// 错误 → ChildClass::showMessage() 的访问级别
// 必须是 public(如同类 ParentClass 中一样)
🗹 扩大可见性
class ParentClass {
protected function showMessage() {
echo "来自父类的消息";
}
}
class ChildClass extends ParentClass {
// ✅ 允许:可见性扩大了
public function showMessage() {
echo "来自子类的消息";
}
}
🗹 数据隐藏
数据隐藏是封装的基本原则。它涉及限制对对象内部状态的直接访问。通过将数据成员(属性)设为私有,我们防止外部代码直接修改它们。这种保护确保数据完整性并防止意外后果。
示例:
class User {
private $password; // 对外部隐藏
}
🗹 封装
将数据(属性)和操作数据的方法(函数)捆绑在一个单元(类)中的概念。它限制对对象某些组件的直接访问,有助于防止对方法和数据的意外干扰和误用。
示例:
class User {
private $password;
public function setPassword($pwd)
{
$this->password = $pwd;
}
public function getPassword()
{
return $this->password;
}
}
👉 简而言之:
🗹 动态多态(运行时)
函数的实际实现在运行时或执行期间决定。(方法重写)
class Animal {
public function speak()
{
echo "动物叫声";
}
}
class Dog extends Animal {
public function speak()
{
echo "汪汪";
}
}
$pet = new Dog();
$pet->speak(); // "汪汪"(运行时决定)
🗹 静态多态(编译时)
函数的实际实现在编译时决定。(方法重载)
__call()
/ __callStatic()
魔术方法进行方法重载class Calculator {
public function add($a, $b, $c = 0) {
return $a + $b + $c;
}
}
$calc = new Calculator();
echo $calc->add(2, 3); // 5
echo $calc->add(2, 3, 4); // 9
🗹 协变
子类方法可以返回比父类更具体(更窄)的类型。
class Animal {}
class Dog extends Animal {}
class ParentClass {
public function getAnimal(): Animal {}
}
class ChildClass extends ParentClass {
public function getAnimal(): Dog {} // ✅ 协变返回类型
}
🗹 逆变
子类方法可以接受比父类更不具体(更宽)的参数类型。
class Animal {}
class Dog extends Animal {}
class ParentClass {
public function setAnimal(Dog $dog) {}
}
class ChildClass extends ParentClass {
public function setAnimal(Animal $animal) {} // ✅ 逆变参数
}
协变:允许子类的方法返回比其父类方法的返回类型更具体的类型。 逆变:允许参数类型在子方法中比其父类中的参数类型更不具体。
注意:PHP 中的协变(返回类型)和逆变(参数类型)直接关系到里氏替换原则(LSP)。它们确保子类可以安全地替换父类而不破坏程序。
🗹 final 属性(从 PHP 8.1 开始)
class A
{
final public string $name = "Ali";
}
class B extends A
{
public string $name = "Omar"; // ❌ 致命错误(不能重写 final 属性)
}
🗹 const
const
定义ClassName::CONST_NAME
访问class A
{
public const VERSION = "1.0";
}
echo A::VERSION; // 1.0
A::VERSION = "2.0"; // ❌ 错误(不能重新赋值常量)
私有属性不能从类外部直接访问。
class User {
private $name = "Ali";
}
$u = new User();
echo $u->name; // ❌ 错误:无法访问私有属性
要允许受控访问,你使用 getter/setter 方法:
class User {
private $name;
public function __construct($name) {
$this->name = $name;
}
public function getName() {
return $this->name;
}
public function setName($name) {
$this->name = $name;
}
}
$u = new User("Ali");
echo $u->getName(); // ✅ Ali
$u->setName("Omar");
echo $u->getName(); // ✅ Omar
🗹 区别
直接访问(public 属性):任何人都可以自由更改值 → 没有控制。 Getter/Setter(private 属性):你控制属性如何被读取或修改。例如,你可以添加验证、日志记录或使其只读。
所以重点是:使用 getter/setter,你添加了封装 → 控制、验证和灵活性。
public function setName($name) {
if (strlen($name) < 3) {
throw new Exception("姓名太短");
}
$this->name = $name;
}
🗹 OOP 范式的缺点
增加复杂性 — 设计类、继承和抽象对于小型/简单项目来说可能是过度设计。
执行速度较慢 — 对象创建和方法调用相比过程式代码增加了开销。
更多内存使用 — 对象存储元数据,可能比简单函数消耗更多内存。
学习曲线更陡峭 — 需要理解 OOP 原则(继承、多态、封装)。
紧耦合风险 — 糟糕的 OOP 设计可能导致僵化、难以更改的代码。
并非总是必需 — 对于小脚本,过程式 PHP 更快更简单。