PHP Fundamentals

Access Modifiers & Encapsulation

13 min Lesson 22 of 45

Understanding Access Modifiers

Access modifiers control the visibility and accessibility of properties and methods in your classes. They are essential for encapsulation, one of the core principles of OOP.

Three Access Modifiers in PHP:
  • public: Accessible from anywhere (inside class, outside class, child classes)
  • protected: Accessible only within the class and child classes
  • private: Accessible only within the class itself

Public Access Modifier

Public properties and methods can be accessed from anywhere:

<?php class User { public $name; // Can be accessed anywhere public $email; // Can be accessed anywhere public function __construct($name, $email) { $this->name = $name; $this->email = $email; } public function greet() { // Can be called anywhere return "Hello, {$this->name}!"; } } $user = new User("Ahmed", "ahmed@example.com"); echo $user->name; // Works fine echo $user->greet(); // Works fine $user->name = "Ali"; // Can modify directly ?>

Private Access Modifier

Private properties and methods can only be accessed from within the class:

<?php class BankAccount { private $balance; // Cannot access from outside private $accountNumber; // Cannot access from outside public function __construct($accountNumber, $initialBalance) { $this->accountNumber = $accountNumber; $this->balance = $initialBalance; } // Public method to access private property public function getBalance() { return $this->balance; } public function deposit($amount) { if ($amount > 0) { $this->balance += $amount; $this->logTransaction("Deposit", $amount); // Call private method return true; } return false; } public function withdraw($amount) { if ($amount > 0 && $amount <= $this->balance) { $this->balance -= $amount; $this->logTransaction("Withdrawal", $amount); return true; } return false; } // Private method - can only be called inside this class private function logTransaction($type, $amount) { echo "Transaction: {$type} of \${$amount} on account {$this->accountNumber}\n"; } } $account = new BankAccount("123456", 1000); echo $account->getBalance(); // Works: 1000 $account->deposit(500); // Works // These will cause errors: // echo $account->balance; // Error: Cannot access private property // echo $account->accountNumber; // Error: Cannot access private property // $account->logTransaction("Test", 100); // Error: Cannot call private method ?>
Why Use Private? Private properties protect data from being modified incorrectly. You control access through public methods, ensuring data integrity.

Protected Access Modifier

Protected properties and methods can be accessed within the class and its child classes:

<?php class Animal { protected $name; // Accessible in this class and child classes protected $age; private $id; // Only accessible in this class public function __construct($name, $age) { $this->name = $name; $this->age = $age; $this->id = uniqid(); } protected function makeSound() { // Can be used by child classes return "Some generic animal sound"; } public function introduce() { return "{$this->name} is {$this->age} years old"; } } class Dog extends Animal { public function bark() { // Can access protected properties from parent return "{$this->name} says: Woof! Woof!"; } public function showInfo() { // Can call protected method from parent return $this->makeSound(); } public function showId() { // Cannot access private property from parent // return $this->id; // This would cause an error } } $dog = new Dog("Max", 3); echo $dog->bark(); // Works fine echo $dog->introduce(); // Works fine // These will cause errors: // echo $dog->name; // Error: Cannot access protected property // echo $dog->makeSound(); // Error: Cannot call protected method ?>

Encapsulation Explained

Encapsulation is the practice of hiding internal data and providing controlled access through methods:

<?php class Product { private $name; private $price; private $discount = 0; public function __construct($name, $price) { $this->name = $name; $this->setPrice($price); // Use setter for validation } // Getter methods (read access) public function getName() { return $this->name; } public function getPrice() { return $this->price; } public function getDiscount() { return $this->discount; } public function getFinalPrice() { return $this->price - ($this->price * $this->discount / 100); } // Setter methods (write access with validation) public function setName($name) { if (strlen($name) >= 3) { $this->name = $name; return true; } return false; } public function setPrice($price) { if ($price > 0) { $this->price = $price; return true; } return false; } public function setDiscount($discount) { if ($discount >= 0 && $discount <= 100) { $this->discount = $discount; return true; } return false; } } $product = new Product("Laptop", 999); echo $product->getName(); // Laptop echo $product->getPrice(); // 999 $product->setDiscount(10); // Valid: 10% discount echo $product->getFinalPrice(); // 899.1 $product->setDiscount(150); // Invalid: ignored $product->setPrice(-100); // Invalid: ignored ?>
Benefits of Encapsulation:
  • Data Validation: Control what values are set
  • Security: Hide sensitive data
  • Flexibility: Change internal implementation without affecting external code
  • Maintainability: Easier to debug and modify

Real-World Example: User Authentication

Here's a practical example using proper encapsulation:

<?php class User { private $username; private $email; private $passwordHash; private $isActive = true; private $loginAttempts = 0; private $maxLoginAttempts = 3; public function __construct($username, $email, $password) { $this->setUsername($username); $this->setEmail($email); $this->setPassword($password); } // Getters public function getUsername() { return $this->username; } public function getEmail() { return $this->email; } public function isActive() { return $this->isActive; } public function getLoginAttempts() { return $this->loginAttempts; } // Setters with validation public function setUsername($username) { if (strlen($username) >= 3 && strlen($username) <= 20) { $this->username = $username; return true; } throw new Exception("Username must be between 3 and 20 characters"); } public function setEmail($email) { if (filter_var($email, FILTER_VALIDATE_EMAIL)) { $this->email = $email; return true; } throw new Exception("Invalid email format"); } private function setPassword($password) { if (strlen($password) >= 8) { $this->passwordHash = password_hash($password, PASSWORD_DEFAULT); return true; } throw new Exception("Password must be at least 8 characters"); } // Authentication methods public function verifyPassword($password) { if (!$this->isActive) { return ["success" => false, "message" => "Account is locked"]; } if (password_verify($password, $this->passwordHash)) { $this->loginAttempts = 0; // Reset attempts return ["success" => true, "message" => "Login successful"]; } $this->loginAttempts++; if ($this->loginAttempts >= $this->maxLoginAttempts) { $this->isActive = false; return ["success" => false, "message" => "Account locked due to too many failed attempts"]; } return ["success" => false, "message" => "Invalid password"]; } public function changePassword($oldPassword, $newPassword) { if (password_verify($oldPassword, $this->passwordHash)) { $this->setPassword($newPassword); return "Password changed successfully"; } return "Current password is incorrect"; } public function resetLoginAttempts() { $this->loginAttempts = 0; $this->isActive = true; } } // Usage try { $user = new User("ahmed123", "ahmed@example.com", "SecurePass123"); // Try login with wrong password $result = $user->verifyPassword("wrongpass"); echo $result["message"] . "\n"; $result = $user->verifyPassword("wrongpass"); echo $result["message"] . "\n"; $result = $user->verifyPassword("wrongpass"); echo $result["message"] . "\n"; // Account locked // Try with correct password (will fail - account locked) $result = $user->verifyPassword("SecurePass123"); echo $result["message"] . "\n"; // Admin resets attempts $user->resetLoginAttempts(); // Now can login $result = $user->verifyPassword("SecurePass123"); echo $result["message"] . "\n"; // Success } catch (Exception $e) { echo "Error: " . $e->getMessage(); } ?>

Property Promotion (PHP 8+)

PHP 8 introduced constructor property promotion, a shorter syntax:

<?php // Traditional way class Product { private $name; private $price; private $category; public function __construct($name, $price, $category) { $this->name = $name; $this->price = $price; $this->category = $category; } } // PHP 8+ way (property promotion) class Product { public function __construct( private string $name, private float $price, private string $category ) {} public function getName(): string { return $this->name; } public function getPrice(): float { return $this->price; } } $product = new Product("Laptop", 999.99, "Electronics"); echo $product->getName(); // Laptop ?>
Property Promotion: Combines parameter declaration and property assignment in one line. Much cleaner and more concise!

Readonly Properties (PHP 8.1+)

Readonly properties can only be initialized once and cannot be modified:

<?php class Configuration { public function __construct( public readonly string $appName, public readonly string $version, public readonly bool $debug ) {} } $config = new Configuration("MyApp", "1.0.0", true); echo $config->appName; // MyApp // This will cause an error: // $config->appName = "NewName"; // Error: Cannot modify readonly property ?>

When to Use Each Modifier

Guidelines:
  • Use private: For internal data that should never be accessed directly (passwords, IDs, internal calculations)
  • Use protected: For data that child classes need to access
  • Use public: For methods that form the class's public API, rarely for properties
  • Default to private: Start with private and only make things more accessible if needed
Exercise:

Create a ShoppingCart class with proper encapsulation:

  • Private property: items (array)
  • Private property: customerEmail
  • Method: addItem($product, $quantity) - validates quantity > 0
  • Method: removeItem($productName)
  • Method: getTotal() - calculates total price
  • Method: getItemCount() - returns number of unique items
  • Method: setCustomerEmail($email) - validates email format
  • Method: getItems() - returns items array

Test by adding items, calculating total, and handling invalid inputs.

Best Practices

Important Guidelines:
  • Principle of Least Privilege: Give minimum necessary access
  • Never expose sensitive data: Keep passwords, tokens, keys private
  • Always validate in setters: Check data before setting properties
  • Use getters/setters: Don't access properties directly from outside
  • Document your API: Make it clear which methods are meant to be used

Summary

In this lesson, you learned:

  • The three access modifiers: public, private, and protected
  • When and why to use each modifier
  • What encapsulation is and why it's important
  • How to create getters and setters for data validation
  • Property promotion and readonly properties (PHP 8+)
  • Best practices for data protection

Next, we'll explore inheritance and polymorphism to build more flexible class hierarchies!