Tính đóng gói (Encapsulation)

Sau khi nắm vững Tính kế thừa (Inheritance), bước tiếp theo là học Tính đóng gói (Encapsulation) – trụ cột quan trọng của OOP. Encapsulation là cơ chế ẩn dữ liệu bên trong class và chỉ cho phép truy cập qua các phương thức công khai (Getter/Setter). Điều này giúp bảo vệ dữ liệu, kiểm soát validation và dễ bảo trì code.

1. Encapsulation là gì?

Encapsulation (Tính đóng gói) là việc gói dữ liệu (properties) và các phương thức xử lý dữ liệu đó vào trong một class, đồng thời ẩn chi tiết bên trong và chỉ cung cấp interface công khai để tương tác.

1.1. Vấn đề khi không có Encapsulation

<?php
// ❌ Không có Encapsulation - dữ liệu không được bảo vệ
class BankAccount {
  public $balance;  // Ai cũng truy cập được
}

$account = new BankAccount();
$account->balance = 1000000;

// Nguy hiểm: Ai cũng có thể sửa trực tiếp
$account->balance = -5000000;  // Số dư âm!?
$account->balance = "abc";     // Kiểu dữ liệu sai!?
$account->balance = 999999999; // Gian lận!?

echo $account->balance;  // Không kiểm soát được
?>

1.2. Giải pháp: Dùng Encapsulation

<?php
// ✅ Có Encapsulation - dữ liệu được bảo vệ
class BankAccount {
  private $balance = 0;  // Ẩn dữ liệu
  
  // Getter - Lấy dữ liệu
  public function getBalance() {
    return $this->balance;
  }
  
  // Setter - Gán dữ liệu có validation
  public function deposit($amount) {
    if ($amount > 0) {
      $this->balance += $amount;
      return true;
    }
    return false;
  }
  
  public function withdraw($amount) {
    if ($amount > 0 && $amount <= $this->balance) {
      $this->balance -= $amount;
      return true;
    }
    return false;
  }
}

$account = new BankAccount();
$account->deposit(1000000);

// ✅ Không thể sửa trực tiếp
// $account->balance = -5000000;  // Lỗi: private

// ✅ Phải dùng methods có validation
$account->withdraw(2000000);  // Từ chối - không đủ tiền
$account->withdraw(500000);   // OK - đủ điều kiện

echo $account->getBalance();  // 500000
?>
ENCAPSULATION - TÍNH ĐÓNG GÓI CLASS BANKACCOUNT 🔒 PRIVATE DATA (Ẩn) private $balance; private $accountNumber; private $password; ❌ Không thể truy cập trực tiếp từ bên ngoài 🔓 PUBLIC INTERFACE (Công khai) public function getBalance() public function deposit($amount) public function withdraw($amount) 💡 Encapsulation = Ẩn dữ liệu + Cung cấp methods an toàn
Hình 1: Khái niệm Encapsulation - Ẩn dữ liệu, cung cấp interface

2. Access Modifiers (Phạm vi truy cập)

PHP có 3 access modifiers để kiểm soát phạm vi truy cập:

ModifierTrong classClass conBên ngoàiKhi nào dùng
publicMethods công khai (Getter/Setter)
protectedDữ liệu cho class + con
privateDữ liệu riêng tư, nhạy cảm
<?php
class User {
  public $name;          // Ai cũng truy cập được
  protected $email;      // Chỉ class này + class con
  private $password;     // Chỉ class này
  
  public function __construct($name, $email, $password) {
    $this->name = $name;
    $this->email = $email;
    $this->password = password_hash($password, PASSWORD_DEFAULT);
  }
  
  // Public method - interface công khai
  public function checkPassword($password) {
    return password_verify($password, $this->password);
  }
  
  // Protected method - dùng trong class + con
  protected function validateEmail($email) {
    return filter_var($email, FILTER_VALIDATE_EMAIL);
  }
  
  // Private method - chỉ dùng trong class này
  private function logActivity($action) {
    error_log("User {$this->name}: $action");
  }
}

$user = new User("An", "an@email.com", "password123");

echo $user->name;                      // ✅ OK - public
// echo $user->email;                  // ❌ Lỗi - protected
// echo $user->password;               // ❌ Lỗi - private
echo $user->checkPassword("abc");     // ✅ OK - public method
?>
ACCESS MODIFIERS PUBLIC public $name; Truy cập: ✅ Trong class ✅ Class con ✅ Bên ngoài PROTECTED protected $email; Truy cập: ✅ Trong class ✅ Class con ❌ Bên ngoài PRIVATE private $password; Truy cập: ✅ Trong class ❌ Class con ❌ Bên ngoài CÁCH SỬ DỤNG Public: Methods công khai (Getter/Setter), interface Protected: Properties/methods cho class + class con Private: Dữ liệu nhạy cảm (password, balance...) 💡 Mặc định dùng Private, chỉ Public khi cần
Hình 2: Access Modifiers - Public, Protected, Private

3. Getter và Setter

Getter (accessor) là method dùng để lấy giá trị của property private/protected.
Setter (mutator) là method dùng để gán giá trị cho property private/protected.

3.1. Cú pháp Getter/Setter

<?php
class Product {
  private $name;
  private $price;
  
  // Getter cho $name
  public function getName() {
    return $this->name;
  }
  
  // Setter cho $name
  public function setName($name) {
    if (strlen($name) >= 3) {
      $this->name = $name;
    } else {
      throw new Exception("Tên phải ít nhất 3 ký tự");
    }
  }
  
  // Getter cho $price
  public function getPrice() {
    return $this->price;
  }
  
  // Setter cho $price
  public function setPrice($price) {
    if ($price > 0) {
      $this->price = $price;
    } else {
      throw new Exception("Giá phải > 0");
    }
  }
}

$product = new Product();
$product->setName("Laptop");
$product->setPrice(15000000);

echo $product->getName();   // Laptop
echo $product->getPrice();  // 15000000

// Validation tự động
try {
  $product->setName("AB");  // Lỗi: quá ngắn
} catch (Exception $e) {
  echo $e->getMessage();
}
?>

3.2. Quy tắc đặt tên Getter/Setter

  • Getter: get + PropertyName() – VD: getName(), getPrice()
  • Setter: set + PropertyName($value) – VD: setName($name)
  • Boolean: is + PropertyName() – VD: isActive(), isPublished()
  • Camel Case: getUserId(), setCreatedAt()
LUỒNG HOẠT ĐỘNG GETTER / SETTER PRIVATE PROPERTY private $email; SETTER - GÁN GIÁ TRỊ setEmail($email) { 1. Nhận giá trị $email = "user@mail.com" 2. Validation if (!filter_var(...)) 3. Gán nếu hợp lệ $this->email = $email Gán GETTER - LẤY GIÁ TRỊ getEmail() { 1. Truy cập property $this->email 2. Xử lý (nếu cần) lowercase, format... 3. Return giá trị Đọc 💡 Setter có validation, Getter trả về an toàn
Hình 3: Luồng hoạt động Getter/Setter

4. Ví dụ Encapsulation thực tế

4.1. Class User với Encapsulation

<?php
class User {
  private $id;
  private $username;
  private $email;
  private $password;
  private $role = "user";
  private $isActive = true;
  private $created_at;
  
  public function __construct($username, $email, $password) {
    $this->setUsername($username);
    $this->setEmail($email);
    $this->setPassword($password);
    $this->created_at = date("Y-m-d H:i:s");
  }
  
  // Getters
  public function getId() {
    return $this->id;
  }
  
  public function getUsername() {
    return $this->username;
  }
  
  public function getEmail() {
    return $this->email;
  }
  
  public function getRole() {
    return $this->role;
  }
  
  public function isActive() {
    return $this->isActive;
  }
  
  // Setters với validation
  public function setUsername($username) {
    $username = trim($username);
    
    if (strlen($username) < 3) {
      throw new Exception("Username phải ít nhất 3 ký tự");
    }
    
    if (!preg_match('/^[a-zA-Z0-9_]+$/', $username)) {
      throw new Exception("Username chỉ chứa chữ, số và _");
    }
    
    $this->username = $username;
  }
  
  public function setEmail($email) {
    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
      throw new Exception("Email không hợp lệ");
    }
    
    $this->email = $email;
  }
  
  public function setPassword($password) {
    if (strlen($password) < 6) {
      throw new Exception("Mật khẩu phải ít nhất 6 ký tự");
    }
    
    $this->password = password_hash($password, PASSWORD_DEFAULT);
  }
  
  public function setRole($role) {
    $allowedRoles = ['user', 'admin', 'moderator'];
    
    if (in_array($role, $allowedRoles)) {
      $this->role = $role;
    } else {
      throw new Exception("Role không hợp lệ");
    }
  }
  
  public function activate() {
    $this->isActive = true;
  }
  
  public function deactivate() {
    $this->isActive = false;
  }
  
  // Method khác
  public function checkPassword($password) {
    return password_verify($password, $this->password);
  }
  
  public function getInfo() {
    return [
      'id' => $this->id,
      'username' => $this->username,
      'email' => $this->email,
      'role' => $this->role,
      'isActive' => $this->isActive,
      'created_at' => $this->created_at
    ];
  }
}

try {
  $user = new User("john_doe", "john@email.com", "password123");
  $user->setRole("admin");
  
  print_r($user->getInfo());
  
  // Không thể truy cập trực tiếp
  // echo $user->password;  // Lỗi
  
  // Phải dùng method
  echo $user->checkPassword("password123") ? "Đúng" : "Sai";
  
} catch (Exception $e) {
  echo "Lỗi: " . $e->getMessage();
}
?>

4.2. Class Product với Encapsulation

<?php
class Product {
  private $id;
  private $name;
  private $price;
  private $quantity;
  private $category;
  private $discount = 0;
  private $isPublished = false;
  
  public function __construct($name, $price, $quantity = 0) {
    $this->setName($name);
    $this->setPrice($price);
    $this->setQuantity($quantity);
  }
  
  // Getters
  public function getName() {
    return $this->name;
  }
  
  public function getPrice() {
    return $this->price;
  }
  
  public function getQuantity() {
    return $this->quantity;
  }
  
  public function getDiscount() {
    return $this->discount;
  }
  
  public function isPublished() {
    return $this->isPublished;
  }
  
  // Setters
  public function setName($name) {
    $name = trim($name);
    
    if (strlen($name) < 3) {
      throw new Exception("Tên sản phẩm phải ít nhất 3 ký tự");
    }
    
    $this->name = $name;
  }
  
  public function setPrice($price) {
    if ($price < 0) {
      throw new Exception("Giá không thể âm");
    }
    
    $this->price = $price;
  }
  
  public function setQuantity($quantity) {
    if ($quantity < 0) {
      throw new Exception("Số lượng không thể âm");
    }
    
    $this->quantity = $quantity;
  }
  
  public function setDiscount($percent) {
    if ($percent < 0 || $percent > 100) {
      throw new Exception("Giảm giá phải từ 0-100%");
    }
    
    $this->discount = $percent;
  }
  
  public function setCategory($category) {
    $this->category = $category;
  }
  
  // Business methods
  public function getFinalPrice() {
    return $this->price * (1 - $this->discount / 100);
  }
  
  public function inStock() {
    return $this->quantity > 0;
  }
  
  public function increaseQuantity($amount) {
    if ($amount <= 0) {
      return false;
    }
    
    $this->quantity += $amount;
    return true;
  }
  
  public function decreaseQuantity($amount) {
    if ($amount <= 0 || $amount > $this->quantity) {
      return false;
    }
    
    $this->quantity -= $amount;
    return true;
  }
  
  public function publish() {
    if (!$this->inStock()) {
      throw new Exception("Không thể publish sản phẩm hết hàng");
    }
    
    $this->isPublished = true;
  }
  
  public function unpublish() {
    $this->isPublished = false;
  }
  
  public function displayInfo() {
    echo "Sản phẩm: {$this->name}
"; echo "Giá gốc: " . number_format($this->price) . " VNĐ
"; if ($this->discount > 0) { echo "Giảm giá: {$this->discount}%
"; echo "Giá sau giảm: " . number_format($this->getFinalPrice()) . " VNĐ
"; } echo "Số lượng: {$this->quantity}
"; echo "Trạng thái: " . ($this->inStock() ? "Còn hàng" : "Hết hàng") . "
"; echo "Hiển thị: " . ($this->isPublished ? "Có" : "Không") . "
"; } } try { $product = new Product("Laptop Dell XPS 13", 25000000, 10); $product->setCategory("Laptop"); $product->setDiscount(10); $product->publish(); $product->displayInfo(); // Bán hàng $product->decreaseQuantity(3); echo "
Sau khi bán 3 cái:
"; echo "Còn lại: " . $product->getQuantity() . "
"; } catch (Exception $e) { echo "Lỗi: " . $e->getMessage(); } ?>

4.3. Class BankAccount với Encapsulation

<?php
class BankAccount {
  private $accountNumber;
  private $accountHolder;
  private $balance;
  private $transactions = [];
  private $isLocked = false;
  
  public function __construct($accountNumber, $accountHolder, $initialBalance = 0) {
    $this->accountNumber = $accountNumber;
    $this->accountHolder = $accountHolder;
    
    if ($initialBalance < 0) {
      throw new Exception("Số dư ban đầu không thể âm");
    }
    
    $this->balance = $initialBalance;
    $this->addTransaction("Mở tài khoản", $initialBalance);
  }
  
  // Getters
  public function getAccountNumber() {
    return $this->accountNumber;
  }
  
  public function getAccountHolder() {
    return $this->accountHolder;
  }
  
  public function getBalance() {
    return $this->balance;
  }
  
  public function isLocked() {
    return $this->isLocked;
  }
  
  public function getTransactions() {
    return $this->transactions;
  }
  
  // Private method - ghi log
  private function addTransaction($type, $amount) {
    $this->transactions[] = [
      'type' => $type,
      'amount' => $amount,
      'balance' => $this->balance,
      'date' => date("Y-m-d H:i:s")
    ];
  }
  
  // Public methods - business logic
  public function deposit($amount) {
    if ($this->isLocked) {
      throw new Exception("Tài khoản đã bị khóa");
    }
    
    if ($amount <= 0) {
      throw new Exception("Số tiền phải > 0");
    }
    
    $this->balance += $amount;
    $this->addTransaction("Nạp tiền", $amount);
    
    return true;
  }
  
  public function withdraw($amount) {
    if ($this->isLocked) {
      throw new Exception("Tài khoản đã bị khóa");
    }
    
    if ($amount <= 0) {
      throw new Exception("Số tiền phải > 0");
    }
    
    if ($amount > $this->balance) {
      throw new Exception("Số dư không đủ");
    }
    
    $this->balance -= $amount;
    $this->addTransaction("Rút tiền", -$amount);
    
    return true;
  }
  
  public function transfer(BankAccount $toAccount, $amount) {
    if ($this->isLocked) {
      throw new Exception("Tài khoản đã bị khóa");
    }
    
    if ($amount <= 0) {
      throw new Exception("Số tiền phải > 0");
    }
    
    if ($amount > $this->balance) {
      throw new Exception("Số dư không đủ");
    }
    
    // Trừ tiền tài khoản nguồn
    $this->balance -= $amount;
    $this->addTransaction("Chuyển khoản đến " . $toAccount->getAccountNumber(), -$amount);
    
    // Cộng tiền tài khoản đích
    $toAccount->deposit($amount);
    
    return true;
  }
  
  public function lock() {
    $this->isLocked = true;
  }
  
  public function unlock() {
    $this->isLocked = false;
  }
  
  public function displayInfo() {
    echo "Số tài khoản: {$this->accountNumber}
"; echo "Chủ tài khoản: {$this->accountHolder}
"; echo "Số dư: " . number_format($this->balance) . " VNĐ
"; echo "Trạng thái: " . ($this->isLocked ? "Đã khóa" : "Hoạt động") . "
"; } public function displayTransactions() { echo "

Lịch sử giao dịch:

"; foreach ($this->transactions as $trans) { echo "{$trans['date']} - {$trans['type']}: " . number_format($trans['amount']) . " VNĐ - " . "Số dư: " . number_format($trans['balance']) . " VNĐ
"; } } } try { $account1 = new BankAccount("1234567890", "Nguyễn Văn A", 5000000); $account2 = new BankAccount("0987654321", "Trần Thị B", 3000000); $account1->deposit(2000000); $account1->withdraw(1000000); $account1->transfer($account2, 500000); echo "Tài khoản 1:
"; $account1->displayInfo(); $account1->displayTransactions(); echo "
"; echo "Tài khoản 2:
"; $account2->displayInfo(); } catch (Exception $e) { echo "Lỗi: " . $e->getMessage(); } ?>

5. Lợi ích của Encapsulation

5.1. Bảo vệ dữ liệu

<?php
class Age {
  private $age;
  
  public function setAge($age) {
    if ($age < 0 || $age > 150) {
      throw new Exception("Tuổi không hợp lệ");
    }
    
    $this->age = $age;
  }
  
  public function getAge() {
    return $this->age;
  }
}

$person = new Age();
$person->setAge(25);    // ✅ OK
// $person->setAge(-5); // ❌ Lỗi - validation
?>

5.2. Dễ bảo trì

<?php
class Price {
  private $price;
  
  // Có thể thay đổi logic bên trong mà không ảnh hưởng code bên ngoài
  public function getPrice() {
    // V1: return $this->price;
    // V2: return $this->price * 1.1;  // Thêm thuế
    // V3: return round($this->price, -3);  // Làm tròn
    return $this->price;
  }
}
?>

5.3. Tính linh hoạt

<?php
class User {
  private $firstName;
  private $lastName;
  
  // Computed property
  public function getFullName() {
    return $this->firstName . " " . $this->lastName;
  }
  
  // Có thể thay đổi cách lưu trữ mà không ảnh hưởng interface
  // VD: Lưu fullName riêng, hoặc tính từ first + last
}
?>

6. Read-only và Write-only Properties

6.1. Read-only (chỉ đọc)

<?php
class Order {
  private $id;
  private $createdAt;
  
  public function __construct() {
    $this->id = uniqid();
    $this->createdAt = date("Y-m-d H:i:s");
  }
  
  // Chỉ có getter, không có setter
  public function getId() {
    return $this->id;
  }
  
  public function getCreatedAt() {
    return $this->createdAt;
  }
}

$order = new Order();
echo $order->getId();  // Chỉ đọc được
// $order->setId(...); // Không có method này
?>

6.2. Write-only (chỉ ghi)

<?php
class Security {
  private $apiKey;
  
  // Chỉ có setter, không có getter
  public function setApiKey($key) {
    $this->apiKey = $key;
  }
  
  public function authenticate() {
    // Dùng $this->apiKey bên trong
    return hash('sha256', $this->apiKey) === "expected_hash";
  }
}

$security = new Security();
$security->setApiKey("secret123");  // Ghi được
// echo $security->getApiKey();     // Không có method này
?>

7. Best Practices

7.1. Quy tắc Encapsulation

NÊN:

  • Properties luôn là private hoặc protected
  • Cung cấp Getter/Setter public khi cần
  • Validate dữ liệu trong Setter
  • Methods công khai là interface, methods riêng là private
  • Tên Getter/Setter theo chuẩn: getName(), setName()

KHÔNG NÊN:

  • Properties public (trừ constants)
  • Getter/Setter cho mọi property (chỉ khi cần)
  • Setter không validate
  • Logic nghiệp vụ phức tạp trong Getter/Setter

7.2. Khi nào không cần Getter/Setter

<?php
class Point {
  public $x;
  public $y;
  
  // ✅ OK - Data class đơn giản, không cần validation
}

class Config {
  public $debug = true;
  public $timezone = "Asia/Ho_Chi_Minh";
  
  // ✅ OK - Config đơn giản, thay đổi trực tiếp OK
}
?>

Tóm tắt

Qua bài này bạn đã nắm được:

  • Encapsulation: Ẩn dữ liệu, cung cấp interface công khai
  • Access Modifiers: public (mọi nơi), protected (class + con), private (chỉ class)
  • Getter: getName() – lấy giá trị property private/protected
  • Setter: setName($value) – gán giá trị có validation
  • Data Hiding: Ẩn chi tiết implement, chỉ lộ interface cần thiết
  • Validation: Kiểm tra dữ liệu trong Setter trước khi gán
  • Lợi ích: Bảo vệ dữ liệu, dễ bảo trì, linh hoạt thay đổi
  • Best Practice: Properties private, methods công khai là interface

Bài tiếp theo, bạn sẽ học về Polymorphism (Tính đa hình) – một object nhiều hình thái.

🎯 Bài tập thực hành

  1. Bài 1: Tạo class Email với private property $email. Tạo setter validate email hợp lệ, getter trả về email, method getDomain() trả về phần sau @.
  2. Bài 2: Tạo class Temperature với private property $celsius. Tạo setter/getter cho Celsius, methods getCelsius(), getFahrenheit(), getKelvin() để chuyển đổi.
  3. Bài 3: Tạo class ShoppingCart với private $items, $total. Methods: addItem($product, $qty), removeItem($productId), getTotal(), getItemCount(). Items không được truy cập trực tiếp.

Gợi ý Bài 1:

<?php
class Email {
  private $email;
  
  public function setEmail($email) {
    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
      throw new Exception("Email không hợp lệ");
    }
    
    $this->email = $email;
  }
  
  public function getEmail() {
    return $this->email;
  }
  
  public function getDomain() {
    if (!$this->email) {
      return null;
    }
    
    $parts = explode('@', $this->email);
    return $parts[1] ?? null;
  }
  
  public function getUsername() {
    if (!$this->email) {
      return null;
    }
    
    $parts = explode('@', $this->email);
    return $parts[0] ?? null;
  }
}

try {
  $email = new Email();
  $email->setEmail("user@example.com");
  
  echo "Email: " . $email->getEmail() . "
"; echo "Username: " . $email->getUsername() . "
"; echo "Domain: " . $email->getDomain() . "
"; $email->setEmail("invalid-email"); // Lỗi } catch (Exception $e) { echo "Lỗi: " . $e->getMessage(); } ?>

Gợi ý Bài 2:

<?php
class Temperature {
  private $celsius;
  
  public function setCelsius($celsius) {
    if ($celsius < -273.15) {
      throw new Exception("Nhiệt độ không thể < -273.15°C");
    }
    
    $this->celsius = $celsius;
  }
  
  public function getCelsius() {
    return $this->celsius;
  }
  
  public function getFahrenheit() {
    return ($this->celsius * 9/5) + 32;
  }
  
  public function getKelvin() {
    return $this->celsius + 273.15;
  }
  
  public function display() {
    echo "Celsius: {$this->getCelsius()}°C
"; echo "Fahrenheit: {$this->getFahrenheit()}°F
"; echo "Kelvin: {$this->getKelvin()}K
"; } } try { $temp = new Temperature(); $temp->setCelsius(25); $temp->display(); } catch (Exception $e) { echo "Lỗi: " . $e->getMessage(); } ?>

Để lại bình luận

Email của bạn sẽ không được hiển thị.