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!