首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 5 可見性修飾符實戰:Public/Protected/Private 保護資料封裝

     編輯:PHP 相關知識

PHP 5 引入物件導向可見性修飾符,為資料封裝提供語言級保障。合理使用 public、protected、private,能防止外部誤用內部狀態、降低耦合、提升可維護性。本文結合資料庫連線、查詢物件等實戰場景,示範可見性的具體應用模式。

三種可見性對照

修飾符存取範圍典型用途
public任意位置對外 API、需被廣泛存取的屬性/方法
protected本類 + 子類內部實現細節、供子類覆寫/擴展的方法
private僅本類核心狀態、不希望被繼承體系觸碰的邏輯

實戰場景:資料庫查詢物件封裝

模擬預處理語句與參數綁定,展示如何用可見性保護內部資源:

<?php
interface DatabaseConnection
{
    public function query(string $sql, array $params = []): ResultSet;
    public function prepare(string $sql): PreparedStatement;
}

class MySqlConnection implements DatabaseConnection
{
    // 私有:連線資源,絕不對外暴露
    private $link = null;
    
    // 受保護:配置參數,子類(如主從分離版)可覆寫
    protected $config = [
        'host' => 'localhost',
        'dbname' => 'app',
        'charset' => 'utf8mb4'
    ];

    public function __construct(array $config = [])
    {
        $this->config = array_merge($this->config, $config);
        $this->connect();
    }

    // 私有:建立連線細節,外部不可呼叫
    private function connect(): void
    {
        $dsn = "mysql:host={$this->config['host']};dbname={$this->config['dbname']};charset={$this->config['charset']}";
        $this->link = new PDO($dsn, $this->config['user'], $this->config['pass'], [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_EMULATE_PREPARES => false,
        ]);
    }

    // 公開:符合介面契約
    public function query(string $sql, array $params = []): ResultSet
    {
        $stmt = $this->link->prepare($sql);
        $stmt->execute($params);
        return new ResultSet($stmt->fetchAll(PDO::FETCH_ASSOC));
    }

    public function prepare(string $sql): PreparedStatement
    {
        return new PreparedStatement($this->link->prepare($sql));
    }
}

/**
 * 查詢物件:封裝單一 SQL 與參數,支援預處理複用
 */
class PreparedStatement
{
    // 私有:PDOStatement 實例,防止外部任意操作
    private $stmt;
    
    // 私有:已綁定參數快取
    private $boundParams = [];

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

    // 公開:綁定參數(支援鏈式呼叫)
    public function bind(string $param, $value, int $type = PDO::PARAM_STR): self
    {
        $this->boundParams[$param] = [$value, $type];
        return $this;
    }

    // 公開:執行並回傳結果集
    public function execute(): ResultSet
    {
        foreach ($this->boundParams as $param => [$value, $type]) {
            $this->stmt->bindValue($param, $value, $type);
        }
        $this->stmt->execute();
        return new ResultSet($this->stmt->fetchAll(PDO::FETCH_ASSOC));
    }

    // 公開:編譯後的 SQL(除錯用,不實際執行)
    public function compile(): string
    {
        $sql = $this->stmt->queryString;
        foreach ($this->boundParams as $param => [$value]) {
            $sql = str_replace($param, is_string($value) ? "'{$value}'" : $value, $sql);
        }
        return $sql;
    }
}

/**
 * 結果集:不可變資料容器
 */
class ResultSet implements IteratorAggregate, Countable
{
    private array $rows;
    private int $position = 0;

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

    public function getIterator(): Traversable
    {
        return new ArrayIterator($this->rows);
    }

    public function count(): int
    {
        return count($this->rows);
    }

    // 僅提供讀取介面,禁止修改內部陣列
    public function first(): ?array
    {
        return $this->rows[0] ?? null;
    }

    public function all(): array
    {
        return $this->rows;
    }
}

使用範例

<?php
$db = new MySqlConnection([
    'user' => 'app_user',
    'pass' => 'secret',
]);

// 1. 直接查詢
$users = $db->query('SELECT * FROM users WHERE status = ?', ['active']);
foreach ($users as $user) {
    echo $user['name'] . "\n";
}

// 2. 預處理複用(批次插入)
$insert = $db->prepare('INSERT INTO logs (level, message) VALUES (:level, :msg)');
$insert->bind(':level', 'info')->bind(':msg', 'User login')->execute();
$insert->bind(':level', 'warn')->bind(':msg', 'Slow query')->execute();

// 3. 編譯檢視(除錯)
echo $insert->compile(); // INSERT INTO logs (level, message) VALUES ('warn', 'Slow query')

// 4. 嘗試存取私有屬性 → Fatal Error
// $db->link->exec('DROP TABLE users'); // 錯誤:Cannot access private property

可見性設計原則

  1. 預設私有:所有屬性預設 private,僅在確需繼承覆寫時改 protected,對外暴露才用 public。
  2. 介面隔離:對外契約用 interface 定義,實現類內部細節全隱藏。
  3. 資料不可變:資料容器(如 ResultSet)僅提供讀取方法,內部陣列 private,防止外部竄改。
  4. 避免 Getter/Setter 濫用:若屬性需對外存取,優先考慮是否應提供領域行為方法(如 $user->changeEmail($new) 而非 $user->setEmail($new))。
  5. 測試友善:單元測試需存取私有屬性時,使用 ReflectionClass 或將測試類放在同一命名空間(PHP 允許同命名空間存取 private),而非放寬為 protected。

常見錯誤與修正

錯誤模式問題修正
所有屬性 public破壞封裝,外部任意修改狀態改 private + 提供精確行為方法
為了測試改 protected洩露實現細節,子類誤用風險用 Reflection 或測試專用 Trait
子類需用父類私有方法複製代碼或違反封裝提取為 protected 方法或獨立服務類
大量 Getter/Setter貧血模型,業務邏輯外泄封裝為領域行為(如 placeOrder())

可見性修飾符不是形式,而是架構意圖的語言表達。嚴守封裝邊界,系統才能在演進中保持穩健。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved