A comprehensive repository demonstrating fundamental Object-Oriented Programming concepts using PHP. This repository serves as a learning resource and reference guide for understanding OOP principles through practical, real-world examples.
- About This Repository
- OOP Concepts Covered
- Getting Started
- Concepts Overview
- Access Modifiers
- Magic Methods
- Namespaces
- Code Examples
- Best Practices
- Contributing
This repository is designed to help developers understand and master Object-Oriented Programming concepts in PHP. Each concept is demonstrated with clear, practical examples that you can run and experiment with. Whether you're a beginner learning OOP for the first time or an experienced developer looking for a quick reference, this repository provides valuable insights into:
- How OOP principles work in PHP
- Real-world use cases for each concept
- Best practices and patterns
- Code organization techniques
- PHP 7.4 or higher
- Basic understanding of PHP syntax
- Composer (optional, for some examples)
- Clone this repository:
git clone <repository-url>
cd oop- Navigate to any concept folder:
cd src/inheritance- Run PHP files directly:
php Model.phpThis repository covers the following core OOP concepts:
- Inheritance - Code reusability through parent-child relationships
- Abstraction - Hiding implementation details with abstract classes
- Interfaces - Defining contracts that classes must implement
- Traits - Horizontal code reuse and shared behavior
- Composition - Building complex objects by combining simpler ones
- Static Methods & Properties - Class-level functionality and utilities
Location: src/inheritance/
Inheritance allows a class to inherit properties and methods from another class, promoting code reusability and establishing an "is-a" relationship.
Key Benefits:
- Code reusability
- Method overriding
- Polymorphism
- Organized class hierarchy
Files:
Employee.php- Recommended for beginners - Real-world Employee/Developer/Manager hierarchy demonstrating inheritance clearlyModel.php- Demonstrates base Model class with inheritance and protected propertiesNotification.php- Shows method overriding with EmailNotification extending Notification
Example:
<?php
class Employee {
protected $name;
protected $id;
protected $salary;
public function __construct($name, $id, $salary) {
$this->name = $name;
$this->id = $id;
$this->salary = $salary;
}
public function takeBreak() {
return "{$this->name} is taking a break.";
}
public function work() {
return "{$this->name} is working on general tasks.";
}
public function getInfo() {
return "ID: {$this->id} | Name: {$this->name} | Salary: \${$this->salary}";
}
}
class Developer extends Employee {
private $language;
public function __construct($name, $id, $salary, $language) {
parent::__construct($name, $id, $salary);
$this->language = $language;
}
public function work() {
return "{$this->name} is writing {$this->language} code.";
}
public function debugCode() {
return "{$this->name} is debugging code.";
}
}
$developer = new Developer('Alice', 'EMP001', 95000, 'PHP');
echo $developer->takeBreak() . "\n";
echo $developer->work() . "\n";
echo $developer->debugCode() . "\n";
echo $developer->getInfo() . "\n";
/* Output:
Alice is taking a break.
Alice is writing PHP code.
Alice is debugging code.
ID: EMP001 | Name: Alice | Salary: $95000
*/Location: src/abstract/
An abstract class is a blueprint for other classes. It cannot be instantiated directly — only extended.
It's used when:
- You want to define a common base behavior (some logic shared by all subclasses)
- But you also want to force subclasses to implement specific methods that differ
An abstract class defines a template that other classes must follow. It can include both implemented methods and concrete methods (declared but not implemented). Classes that extend it must implement all abstract methods.
Key Characteristics:
- Cannot be instantiated directly
- Can contain both abstract and concrete methods
- Forces child classes to implement abstract methods
- Great for template method patterns
Files:
Controller.php- Base controller with shared response methodsModel.php- Abstract base model with common traits and behaviorsPayment.php- Payment gateway with template method pattern
Example:
<?php
abstract class PaymentGateway {
public function process() {
$this->connect();
$this->pay();
$this->disconnect();
}
protected function connect() {
echo "Connecting to gateway...\n";
}
protected function disconnect() {
echo "Disconnecting...\n";
}
abstract protected function pay();
}
class StripeGateway extends PaymentGateway {
protected function pay() {
echo "Processing Stripe payment...\n";
}
}
$gateway = new StripeGateway();
$gateway->process();
/* Output:
Connecting to gateway...
Processing Stripe payment...
Disconnecting...
*/Location: src/interface/
Interfaces define contracts that classes must follow. They specify what methods a class must implement without providing the implementation itself.
Key Points:
- It defines what methods a class must implement — but not how they work
- Only constants are allowed as properties
- Multiple interfaces can be implemented by a single class
- It's used to decouple code and enforce consistency
- All methods are implicitly abstract (no implementation)
- Enforces consistent behavior across different classes
Files:
Payment.php- Payment gateway interface with multiple implementationsTemplate.php- Template interface demonstrating required method implementationWithAbstractExample.php- Interface implementation using abstract classes
Example:
<?php
interface MainPaymentGateway {
public function charge(float $amount);
public function refund(string $transactionId);
}
class StripePayment implements MainPaymentGateway {
public function charge(float $amount) {
echo "Charging \${$amount} via Stripe\n";
}
public function refund(string $transactionId) {
echo "Refunding {$transactionId} via Stripe\n";
}
}
class PayPalPayment implements MainPaymentGateway {
public function charge(float $amount) {
echo "Charging \${$amount} via PayPal\n";
}
public function refund(string $transactionId) {
echo "Refunding {$transactionId} via PayPal\n";
}
}
$stripe = new StripePayment();
$stripe->charge(100.50);
$stripe->refund("TXN123");
$paypal = new PayPalPayment();
$paypal->charge(200.00);
$paypal->refund("TXN456");
/* Output:
Charging $100.5 via Stripe
Refunding TXN123 via Stripe
Charging $200 via PayPal
Refunding TXN456 via PayPal
*/Location: src/traits/
A Trait is a mechanism for code reuse in single inheritance languages like PHP. It allows you to inject reusable methods into multiple unrelated classes without using inheritance.
Why Traits Exist (The Real Problem They Solve)
PHP supports single inheritance — a class can only extend one parent class. But what if you want to reuse logic across multiple unrelated classes?
For example:
- You want to reuse a
log()method in multiple classes (User, Product, Order) - You don't want them all to extend a BaseLogger class (that would be wrong semantically)
Some people refer to traits as "like an automatic CTRL+C/CTRL+V for your classes". You specify some methods in a trait and "import" them into your class. It will make your code behave like the methods were written inside your class.
Key Characteristics:
- Can be used by multiple classes
- Provides actual method implementations
- Helps avoid limitations of single inheritance
- Ideal for cross-cutting concerns
- Can have properties and method implementations
- Supports code reuse and composition
Files:
ApiResponse.php- API response formatting trait for controllerslogger.php- Logging functionality traittrait-vs-interface.php- Comparison between traits and interfaces
Example:
<?php
trait LoggerTrait {
public function log(string $message) {
echo "[" . date('Y-m-d H:i:s') . "] $message\n";
}
}
class User {
use LoggerTrait;
public function register() {
$this->log("User registered successfully!");
}
}
class Product {
use LoggerTrait;
public function add() {
$this->log("Product added successfully!");
}
}
$user = new User();
$user->register();
$product = new Product();
$product->add();
/* Output:
[2024-01-15 10:30:45] User registered successfully!
[2024-01-15 10:30:45] Product added successfully!
*/Location: src/composition/
Composition is an OOP design principle where objects are built by combining other objects, instead of relying on inheritance.
Key Points:
- Promotes flexibility, modularity, and testability
- Frameworks like Laravel use composition everywhere — controllers have services, mailers have transports, and jobs have queues
- It's the foundation of dependency injection and design patterns like Decorator and Strategy
- More flexible than inheritance
- Easier to change behavior at runtime
- Avoids deep inheritance hierarchies
- Follows "favor composition over inheritance" principle
Files:
Subscription.php- Demonstrates composition with BillingPortal dependency
Example:
<?php
interface BillingPortal {
public function getCustomer();
public function getSubscription();
}
class StripeBillingPortal implements BillingPortal {
public function getCustomer() {
echo "Fetching customer from Stripe...\n";
return "Customer ID: STRIPE_123";
}
public function getSubscription() {
echo "Fetching subscription from Stripe...\n";
return "Subscription ID: SUB_456";
}
}
class Subscription {
protected BillingPortal $billingPortal;
public function __construct(BillingPortal $billingPortal) {
$this->billingPortal = $billingPortal;
}
public function create() {
$customer = $this->billingPortal->getCustomer();
$subscription = $this->billingPortal->getSubscription();
echo "Subscription created!\n";
}
}
$subscription = new Subscription(new StripeBillingPortal());
$subscription->create();
/* Output:
Fetching customer from Stripe...
Fetching subscription from Stripe...
Subscription created!
*/Location: src/static-methods/
A static method belongs to the class itself, not to any specific object (instance). You call it using the class name — not $object->method(), but ClassName::method().
Key Characteristics:
- Accessed using
Class::method()syntax - Shared across all instances
- Useful for utility functions
- Cannot use
$this(no instance available) - Can be overridden in subclasses
Q: Can static methods use $this?
A: No. $this refers to the current object instance, and static methods don't have one.
Q: What is "Late Static Binding"?
A: When we use static::, PHP resolves it dynamically at runtime to the calling class, not the class where it's defined.
self:: vs static:: - Common Interview Question:
| Keyword | Resolution | When Used |
|---|---|---|
self:: |
Refers to the class where it's written (compile-time) | Non-inheritable, fixed to the class definition |
static:: |
Refers to the class that's calling it (runtime) | Inheritable, late static binding |
Files:
counter.php- Static property for counting instancesvalidation.php- Static utility methods for validationlate-static-binding.php- Demonstrates late static binding concept
Example:
<?php
class Student {
public $name;
public $id;
public static $count = 0;
public function __construct($name) {
self::$count++;
$this->id = self::$count;
$this->name = $name;
}
public function show() {
echo "ID: $this->id, Name: $this->name\n";
}
}
$stu1 = new Student("Alice");
$stu2 = new Student("Bob");
$stu3 = new Student("Charlie");
$stu1->show();
$stu2->show();
$stu3->show();
echo "Total students: " . Student::$count . "\n";
/* Output:
ID: 1, Name: Alice
ID: 2, Name: Bob
ID: 3, Name: Charlie
Total students: 3
*/Late Static Binding Example:
<?php
class A {
public static function who() {
echo __CLASS__;
}
public static function test() {
self::who();
static::who();
}
}
class B extends A {
public static function who() {
echo __CLASS__;
}
}
B::test();
echo "\n";
A::test();
/* Output:
AB
AA
*/Access modifiers control the visibility and accessibility of properties and methods in PHP classes. Understanding when to use each modifier is crucial for proper encapsulation and object-oriented design.
public is used when you want a property or method to be accessible from anywhere — both inside and outside the class.
It defines the external API of your class — the methods and properties other parts of the system are allowed to use. In real-world frameworks (like Laravel or Symfony), all public methods in controllers or services form the class's "contract" — these are the endpoints other code can safely call.
<?php
class User {
public $name;
public function getName() {
return $this->name;
}
}
$user = new User();
$user->name = "John";
echo $user->getName();
/* Output:
John
*/protected is used when you want to hide a property or method from external code, but still allow subclasses to access or override it.
It's common in frameworks for base classes that define reusable patterns, where child classes need to extend or customize internal behavior without exposing those internals publicly.
<?php
class Vehicle {
protected $speed;
protected function accelerate() {
echo "Accelerating...\n";
}
}
class Car extends Vehicle {
public function drive() {
$this->speed = 60;
$this->accelerate();
echo "Driving at {$this->speed} km/h\n";
}
}
$car = new Car();
$car->drive();
/* Output:
Accelerating...
Driving at 60 km/h
*/private is used when a property or method should be completely hidden from outside access, including child classes.
It's for internal implementation details that must never be overridden or exposed — used to enforce strict encapsulation and prevent misuse or accidental modification of internal state.
<?php
class BankAccount {
private $balance = 0;
private function validateAmount($amount) {
return $amount > 0;
}
public function deposit($amount) {
if ($this->validateAmount($amount)) {
$this->balance += $amount;
echo "Deposited \${$amount}. New balance: \${$this->balance}\n";
} else {
echo "Invalid amount\n";
}
}
public function getBalance() {
return $this->balance;
}
}
$account = new BankAccount();
$account->deposit(1000);
echo "Balance: \${$account->getBalance()}\n";
/* Output:
Deposited $1000. New balance: $1000
Balance: $1000
*/| Modifier | Class Itself | Child Classes | Outside Code | Use Case |
|---|---|---|---|---|
| public | ✓ | ✓ | ✓ | External API, methods meant to be called by other code |
| protected | ✓ | ✓ | ✗ | Internal implementation shared with child classes |
| private | ✓ | ✗ | ✗ | Internal details that must never be exposed |
Magic methods are special methods in PHP that are automatically called in response to certain events or actions. They always start with double underscores (__).
Below are the three most commonly used magic methods:
When called: Automatically when an object is created.
Use: Initialize object properties or inject dependencies.
Real-world: Setting up models, database connections, or configs.
<?php
class User {
private $name;
public function __construct($name) {
$this->name = $name;
echo "User {$this->name} created\n";
}
}
$user = new User("John");
/* Output:
User John created
*/When called: When the object is destroyed or script ends.
Use: Cleanup logic — close connections, delete temp files, release resources.
Real-world: Close file handles, DB connections, or log object destruction.
<?php
class DatabaseConnection {
private $connection;
public function __construct() {
$this->connection = "Connected to database";
echo "Database connection opened\n";
}
public function __destruct() {
echo "Closing database connection...\n";
}
}
$db = new DatabaseConnection();
unset($db);
/* Output:
Database connection opened
Closing database connection...
*/When called: When an object is used like a function ($obj()).
Use: Make objects callable, ideal for single-action classes.
Real-world: Laravel single-action controllers (Route::get('list', Controller::class)).
<?php
class SingleActionController {
public function __invoke($request) {
return "Handled request: " . $request;
}
}
$controller = new SingleActionController();
$result = $controller("GET /users");
echo $result . "\n";
/* Output:
Handled request: GET /users
*/PHP provides many more magic methods for advanced use cases. For a complete list and detailed documentation, visit:
PHP Official Documentation - Magic Methods
Some other commonly used magic methods include:
__get()- Accessing undefined or private properties__set()- Writing to undefined or private properties__call()- Calling undefined instance methods__callStatic()- Calling undefined static methods__toString()- Converting object to string__clone()- Object cloning__sleep()/__wakeup()- Serialization- And more...
A namespace in PHP is a way to group related classes, interfaces, traits, functions, or constants under a unique name. It helps avoid naming conflicts and organizes code logically.
Why Namespaces Matter:
Laravel relies heavily on namespaces to map its directory structure to PHP class names, enabling automatic dependency injection, autoloading, and clean architecture.
<?php
namespace App\Http\Controllers;
class UserController {
public function index() {
return "User list";
}
}
$controller = new \App\Http\Controllers\UserController();
echo $controller->index();
/* Output:
User list
*/<?php
namespace App\Services;
use App\Http\Controllers\UserController;
use App\Models\User as UserModel;
class UserService {
public function __construct(UserController $controller) {
$this->controller = $controller;
}
}In composer.json, you can define PSR-4 autoloading:
{
"autoload": {
"psr-4": {
"App\\": "app/"
}
}
}What this means: When Composer sees App\Something, it will look for that class inside the app/ folder.
With PSR-4 autoloading, the namespace structure matches the directory structure:
app/
├── Http/
│ └── Controllers/
│ └── UserController.php → namespace App\Http\Controllers;
├── Models/
│ └── User.php → namespace App\Models;
└── Services/
└── UserService.php → namespace App\Services;
- Avoid Naming Conflicts: Different namespaces can have classes with the same name
- Organize Code: Logical grouping of related classes
- Autoloading: Automatic class loading based on namespace
- Framework Integration: Essential for Laravel, Symfony, and other modern PHP frameworks
Each concept folder contains working PHP examples that you can run and modify:
src/
├── abstract/ # Abstract classes and methods
├── composition/ # Composition patterns
├── inheritance/ # Class inheritance examples
├── interface/ # Interface implementations
├── static-methods/ # Static methods and properties
└── traits/ # Trait usage examples
Navigate to any concept folder and run the PHP files:
cd src/inheritance
php Model.php
cd src/interface
php Payment.php
cd src/traits
php ApiResponse.php| Feature | Interface | Abstract Class |
|---|---|---|
| Instantiation | Cannot be instantiated | Cannot be instantiated |
| Methods | Only abstract methods | Can have abstract and concrete methods |
| Properties | Constants only | Can have properties |
| Multiple Inheritance | Yes (multiple interfaces) | No (single inheritance) |
| Access Modifiers | Public only | Can have protected/private |
| Use Case | Define contracts | Shared code with required implementations |
| Feature | Trait | Interface |
|---|---|---|
| Purpose | Share reusable code (behavior) | Define a contract (what must be implemented) |
| Implementation | Actual method implementations + properties | Only method signatures (no code) |
| Multiple Usage | Yes | Yes |
| Properties | Can have properties | Constants only |
| Constructor | Can have constructor | No constructor |
| Use Case | Code reuse and composition | Enforcing structure and consistency |
| Feature | Composition | Inheritance |
|---|---|---|
| Relationship | "has-a" | "is-a" |
| Flexibility | High (runtime changeable) | Low (compile-time) |
| Coupling | Loose | Tight |
| Use Case | Prefer when relationship is dynamic | Prefer when relationship is permanent |
- Use Inheritance when you have a clear "is-a" relationship and want to share code
- Use Composition when you need flexibility and want to change behavior at runtime
- Use Interfaces to define contracts and enforce consistent behavior
- Use Traits for horizontal code reuse when inheritance doesn't fit
- Use Abstract Classes when you need to share code and enforce method implementation
- Prefer Composition over Inheritance for better flexibility and maintainability
- Keep Inheritance Hierarchies Shallow - avoid deep inheritance chains
- Use Static Methods for utility functions that don't require instance state
Contributions are welcome! If you'd like to improve examples or add new concepts:
- Fork the repository
- Create a feature branch
- Add your examples with clear comments
- Submit a pull request
This repository is for educational purposes. Feel free to use and modify the code examples.
This repository is maintained for educational purposes. If you find it helpful, consider giving it a star!