← PHP 封装:把数据和方法“打包”的艺术 PHP Final关键词:锁定类和方法,禁止继承与重写 →

PHP 访问修饰符:public、private、protected 的权限边界

原创 2026-05-12 PHP 已有人查阅

在PHP的面向对象编程里,publicprivateprotected 这三个关键词统称为访问修饰符(Access Modifiers)。它们像是类成员的三道门禁卡,精确控制着属性和方法在代码各处能否被访问。每当你声明一个成员变量或成员方法时,前面都会加上这三个词之一,以此定义它的可见范围。

访问修饰符是实现封装(Encapsulation)的语法基础。封装的核心思路是:把对象的内部数据对外界隐藏起来,只允许通过类提供的公开方法进行受控访问。这样一来,外部代码无法直接修改对象状态,数据的安全性和完整性就有了保障。

三个修饰符的权限一览

用通俗的话来理解它们各自的管辖范围:

  • public:开放,类内部、子类、外部代码都可以访问。

  • protected:家族内部开放,类自身和其子类可以访问,外部代码禁止。

  • private:封闭,只有定义它的那个类自己可以访问,子类都不行。

下面这张对照表让你快速看清它们的区别:

特性 / 修饰符 public protected private
可见范围 任何地方 类自身及子类 仅限定义它的类内部
同类内访问
子类中访问
外部代码访问
典型用途 对外公开的 API 方法 继承链用的辅助方法 内部实现细节、敏感数据
继承后 继承且可访问 继承且可访问 继承但不可访问
封装强度

Public:对外敞开的接口

PHP 中,类成员默认就是 public。标记为 public 的属性和方法可以在任何地方被调用——类内部、子类、甚至不相关的代码段。看下面这个简单例子:

class Book {
    public $title;
    public $price;

    public function __construct(string $title = "PHP Basics", int $price = 380) {
        $this->title = $title;
        $this->price = $price;
    }

    public function getPrice() {
        echo "Price: $this->price\n";
    }

    public function getTitle() {
        echo "Title: $this->title\n";
    }
}

$book1 = new Book();
echo "Title: {$book1->title} | Price: {$book1->price}";

输出:

Title: PHP Basics | Price: 380

你可以直接 $book1->title 拿到属性值,也可以通过 $book1->getTitle() 方法获取。全公开确实方便,但这也意味着任何代码都可以随意读写对象的状态。我个人在写一些原型或一次性脚本时偶尔会这么干,但在正式项目里,把属性设为 public 等于放弃了封装带来的保护——将来如果要加数据校验或格式转换,你就得在整个代码库里搜寻所有直接访问属性的地方。

Private:强的数据保护

private 把访问权限收束到极窄的范围:只有定义这个成员的类自己能访问,子类和外部代码一概触碰不到。这才是封装理念的标准实现——属性设为 private,通过 public 方法暴露读写入口:

class Book {
    private $title;
    private $price;

    public function __construct(string $title = "PHP Basics", int $price = 380) {
        $this->title = $title;
        $this->price = $price;
    }

    public function getTitle() {
        echo "Title: $this->title\n";
    }

    public function getPrice() {
        echo "Price: $this->price\n";
    }
}

$book1 = new Book();
$book1->getTitle();
$book1->getPrice();

// 下面这行会报错
// echo "Title: $book1->title Price: $book1->price";

输出:

Title: PHP Basics
Price: 380

如果试图直接 $book1->title,PHP 会抛出一个致命错误,因为 $title 是 private 的,外部代码看不见它。这种模式的价值在于:你可以在 getPrice() 里随时加入折扣逻辑、格式化、日志记录,调用方的代码一行都不用改。相比直接暴露裸属性,private 加公开方法的方式赋予了类对自身数据百分之百的控制权。

Protected:为继承预留的通道

protected 的权限恰好卡在 public 和 private 之间:外部代码不能访问,但子类可以。当你在设计一个需要被子类扩展的类族时,protected 就显得特别有用。

把之前 Book 类的 $title 改成 protected$price 保持 private

class Book {
    private $price;
    protected $title;

    function __construct(string $param1 = "PHP Basics", int $param2 = 380) {
        $this->title = $param1;
        $this->price = $param2;
    }

    public function getPrice() {
        echo "Price: $this->price <br/>";
    }

    public function getTitle() {
        echo "Title: $this->title <br/>";
    }
}

$b1 = new Book();
$b1->getTitle();
$b1->getPrice();

同一个类的公开方法访问 private 和 protected 属性都正常。现在让一个子类继承 Book

class mybook extends Book {
    // 没有额外定义成员
}

$childBook = new mybook();
$childBook->getTitle();  // 正常,因为 $title 是 protected

由于子类继承了父类的 public 和 protected 成员,mybook 的对象可以顺利调用从父类继承来的 getTitle()

但关键点来了:如果 mybook 不是 Book 的子类,而是个独立的类,它试图从外部访问 protected 成员就会直接报错:

class mybook {
    public function getmytitle($b) {
        echo "Title: $b->title <br/>";
    }
}

$b1 = new mybook();
$b = new Book();
$b1->getmytitle($b);

输出:

Fatal error: Uncaught Error: Cannot access protected property Book::$title

mybook 没有继承 Book,它站在“外部代码”的位置,所以无权触碰 $title。我当初学到这里时犯过好几次这个错误,总以为只要是自己写的类就能访问别的类的 protected 成员,其实这个修饰符的“受保护”是针对类族外部而言的。

综合示例:银行账户的权限设计

下面用一个银行账户的场景,把三个修饰符放在同一个例子中对比。这个例子很贴近实际业务逻辑,能帮你理清什么时候该用哪个修饰符:

class BankAccount {
    public $accountHolder = "John Doe";
    protected $accountBalance = 10000;
    private $pinCode = 1234;

    public function getAccountInfo() {
        echo "Account Holder Name is ";
        echo $this->accountHolder;
        echo "\n";

        echo "Account Balance is ";
        echo $this->accountBalance;
        echo "\n";

        echo "Pin Code is ";
        echo $this->pinCode;
        echo "\n";
    }

    protected function calculateInterest() {
        $interest = $this->accountBalance * 0.05;
        echo "Annual Interest is ";
        echo $interest;
        echo "\n";
    }

    private function showPrivateNote() {
        echo "This is a private internal note\n";
    }

    public function accessPrivateMethod() {
        $this->showPrivateNote();
    }
}

class SavingsAccount extends BankAccount {
    public function displayAccountSummary() {
        echo "Welcome ";
        echo $this->accountHolder;
        echo "\n";

        echo "Your current balance is ";
        echo $this->accountBalance;
        echo "\n";

        echo "Calculating Interest\n";
        $this->calculateInterest();
    }

    public function tryAccessPrivate() {
        echo "Trying to access private data\n";
        // 下面两行如果取消注释都会报错
        // echo $this->pinCode;
        // $this->showPrivateNote();
    }
}

$customer = new SavingsAccount();

$customer->displayAccountSummary();
echo "\n";

$customer->getAccountInfo();
echo "\n";

$customer->accessPrivateMethod();
echo "\n";

// 下面这些外部访问会报错
// echo $customer->accountBalance;
// echo $customer->pinCode;

输出:

Welcome John Doe
Your current balance is 10000
Calculating Interest
Annual Interest is 500

Account Holder Name is John Doe
Account Balance is 10000
Pin Code is 1234

This is a private internal note

从这个例子可以清晰看出三个层次的权限逻辑:

  • $accountHolder 是 public:任何地方都能读取,适合公开的信息,比如户主姓名。

  • $accountBalance 是 protected:外部代码不能直接碰,但 SavingsAccount 子类可以通过 displayAccountSummary() 正常使用它来计算利息。适合需要在继承链享但不对外暴露的数据。

  • $pinCode 是 private:连 SavingsAccount 都碰不到,只有 BankAccount 自己的 getAccountInfo() 方法能访问。适合密码、密钥这类绝不应该被任何子类或外部代码触及的敏感数据。

同样地,calculateInterest() 设为 protected 让子类能调用但外部不行,showPrivateNote() 设为 private 则彻底封闭,只通过 accessPrivateMethod() 这个公开入口间接调用。

什么时候不用 public,而是用 private 或 protected

这是我在带新人时被问得比较多的问题。直接给出我的判断思路:

  • 如果你确定某个成员会在整个应用程序的任何角落被调用,那就 public。但要谨慎,因为一旦公开,后续改动就可能影响远程调用方。

  • 如果你只是想让子类共享某些逻辑,但不希望外部代码直接调用,那就 protected。利息计算就是个好例子——银行内部的计算逻辑应该给各类型账户子类用,但不该让随便哪个外部脚本去触发。

  • 如果某个成员纯粹是类的内部辅助工具,或者存放的是敏感数据(密码、密钥、内部状态标记),一律 private。这是封装强度的顶端。

一个我个人踩过的坑:早期写代码时图省事,把很多属性和方法都设成 public,结果项目迭代几版之后,根本搞不清哪些外部代码依赖了这些公开成员,重构时寸步难行。后来养成了一个习惯——新写的类,默认所有属性 private,所有方法 private 或 protected,只有当外部真正需要调用时才逐步开放为 public。这个“默认小权限”的思路在PHP封装设计里帮我绕开了大量隐患。

本节课程知识要点

  • PHP 的访问修饰符有三个:public(公开)、protected(受保护)、private(私有),控制类成员的可见范围。

  • public 成员任何地方都能访问,适合对外 API;protected 仅限类自身和其子类访问,适合继承链中的共享逻辑;private 只有定义它的类能访问,适合内部实现和敏感数据。

  • 子类可以继承 private 成员,但无法直接访问它们,这是封装隔离性的重要体现。

  • 养成“默认小权限”的习惯,优先使用 private 或 protected,只在必要时开放为 public,有助于构建可维护的系统。

← PHP 封装:把数据和方法“打包”的艺术 PHP Final关键词:锁定类和方法,禁止继承与重写 →
分享笔记 (共有 篇笔记)
验证码:
微信公众号