Jul 1, 2024 12 minute read
Object-Oriented Principles in PHP
Table of Contents
- What is Object-Oriented Programming (OOP)?
- Fundamental Concept
- Encapsulation
- Inheritance
- Abstract Classes
- Interface
- Object Composition
- Abstraction
- Value Objects
- Mutability
- Exceptions
- Useful Links
What is Object-Oriented Programming (OOP)?
OOP is a programming methodology that is centered around the concept of objects. Objects are instances of classes, which can contain both data (attributes or properties) and functions (methods). OOP is used to structure a software program into simple, reusable pieces of code blueprints (usually classes), which are then used to create individual instances of objects.
In this blog post, I will be talking about Object-Oriented Principles, which are the fundamental concepts that support and guide the practice of Object-Oriented Programming. Those principles inform and shape how OOP is implemented, ensuring that the code is well-organized, reusable, and maintainable.
Fundamental Concept
In OOP, a class and an object are fundamental concepts. Here’s a simple explanation of each:
Classes
A class is like a blueprint or template for creating objects. It defines the properties (attributes) and behaviors (methods) that the objects created from the class will have. Think of a class as a recipe for a cake. The recipe itself isn’t a cake, but it tells you how to make one.
In PHP, a class might look like this:
class Car {
// Properties
public $color;
public $model;
// Constructor (optional, for initializing properties)
public function __construct($color, $model) {
$this->color = $color;
$this->model = $model;
}
// Method (behavior)
public function display() {
return "This car is a " . $this->color . " " . $this->model . ".";
}
}
Objects
An object is an instance of a class. It’s a specific implementation of the class with actual values for the properties defined in the class. Continuing with the cake analogy, an object is like the actual cake you bake using the recipe.
In PHP, you create (instantiate) an object from a class like this:
// Creating an object from the Car class
$myCar = new Car("red", "Toyota");
// Using the object's method
echo $myCar->display(); // Outputs: This car is a red Toyota.
Classes provide the structure and definition, while objects are the actual entities that use that structure.
Encapsulation
Encapsulation is one of the core principles of OOP. It ensures that the internal representation of an object is hidden from the outside. Only the data necessary for interfacing with the object is exposed.
In PHP, encapsulation is achieved using visibility keywords: public, protected and private.
These keywords control the access levels to the properties and methods of a class.
-
public
: Accessible from anywhere. -
protected
: Accessible within the class and its subclasses. -
private
: Accessible only within the class itself.
Here’s a simple example to illustrate encapsulation:
class Car {
private $color;
public function setColor($color) {
$this->color = $color;
}
public function getColor() {
return $this->color;
}
}
$myCar = new Car();
$myCar->setColor('Red');
echo $myCar->getColor(); // Outputs: Red
Encapsulation helps to protect an object’s internal state from unintended or harmful interference and allows for controlled and predictable interactions with the object.
Inheritance
Inheritance allows a class (called a child or subclass) to inherit properties and methods from another class (called a parent or superclass). This promotes code reusability and establishes a hierarchical relationship between classes.
In PHP, inheritance is implemented using the extends keyword. The child class inherits all public and protected properties and methods from the parent class but can also have its own additional properties and methods. Moreover, it can override the inherited methods to provide specific implementations.
Here's a simple example to illustrate inheritance in PHP:
// Parent class
class Vehicle {
public $brand;
public function __construct($brand) {
$this->brand = $brand;
}
public function displayBrand() {
return "Brand: " . $this->brand;
}
}
// Child class inheriting from Vehicle
class Car extends Vehicle {
public $model;
public function __construct($brand, $model) {
parent::__construct($brand); // Call parent constructor
$this->model = $model;
}
public function displayModel() {
return "Model: " . $this->model;
}
}
// Create an object of the Car class
$myCar = new Car("Toyota", "Corolla");
// Access methods from both the parent and child classes
echo $myCar->displayBrand(); // Outputs: Brand: Toyota
echo $myCar->displayModel(); // Outputs: Model: Corolla
In this example:
- The Vehicle class is the parent class with a property $brand and a method displayBrand().
- The Car class is the child class that inherits from Vehicle and adds an additional property $model and a method displayModel().
- The Car class also calls the parent class constructor using parent::__construct($brand) to initialize the inherited $brand property.
Abstract Classes
Abstract classes in PHP, are classes that cannot be instantiated on their own. They are meant to be extended by other classes. An abstract class can contain both abstract methods (methods without implementation) and regular methods (methods with implementation). Abstract methods must be defined in any child class that extends the abstract class.
Here's an example of using abstract classes in PHP:
// Abstract class
abstract class Animal {
// Abstract method (no implementation)
abstract public function makeSound();
// Regular method
public function sleep() {
return "Sleeping...";
}
}
// Child class inheriting from Animal
class Dog extends Animal {
// Implement the abstract method
public function makeSound() {
return "Bark";
}
}
// Child class inheriting from Animal
class Cat extends Animal {
// Implement the abstract method
public function makeSound() {
return "Meow";
}
}
// Create objects of the child classes
$dog = new Dog();
$cat = new Cat();
// Call methods
echo $dog->makeSound(); // Outputs: Bark
echo $dog->sleep(); // Outputs: Sleeping...
echo $cat->makeSound(); // Outputs: Meow
echo $cat->sleep(); // Outputs: Sleeping...
In this example:
- Animal is an abstract class with one abstract method makeSound() and one regular method sleep().
- Dog and Cat are concrete classes that extend the Animal class and provide implementations for the abstract method makeSound().
- We create objects of the Dog and Cat classes and call both the implemented abstract method makeSound() and the inherited regular method sleep().
Abstract classes allow you to define a template for future subclasses while ensuring that certain methods are implemented in those subclasses.
Interface
In PHP, an interface is a contract that defines a set of methods that a class must implement. Unlike abstract classes, interfaces cannot contain any properties or method implementations; they only define method signatures.
Classes that implement an interface must provide concrete implementations for all the methods declared in the interface. Interfaces are useful for defining common functionalities that different classes should share, ensuring that these classes implement the methods defined in the interface.
Here's an example of using interfaces in PHP:
// Define an interface
interface AnimalInterface {
public function makeSound();
public function move();
}
// Class implementing the interface
class Dog implements AnimalInterface {
public function makeSound() {
return "Bark";
}
public function move() {
return "Run";
}
}
// Another class implementing the same interface
class Cat implements AnimalInterface {
public function makeSound() {
return "Meow";
}
public function move() {
return "Jump";
}
}
// Create objects of the implementing classes
$dog = new Dog();
$cat = new Cat();
// Call the methods defined in the interface
echo $dog->makeSound(); // Outputs: Bark
echo $dog->move(); // Outputs: Run
echo $cat->makeSound(); // Outputs: Meow
echo $cat->move(); // Outputs: Jump
Handshakes
The concept of a "handshake" in the context of interfaces is essentially about ensuring that any class implementing an interface follows the contract defined by the interface.
This guarantees that all classes adhering to the interface can be used interchangeably when they are expected to implement the same functionality.
In the example above:
- The AnimalInterface interface defines a contract with makeSound() and move() methods.
- Both Dog and Cat classes implement this interface, meaning they must define these methods.
- When you create a Dog or Cat object, you can be sure that they have makeSound() and move() methods, which are the handshakes or agreements that these classes must fulfill.
Using interfaces, you can write more flexible and maintainable code by relying on the defined contracts rather than specific implementations. This is especially useful in large applications where different parts of the system may be developed independently.
Object Composition
Object composition is a design principle where one class is composed of one or more objects of other classes, rather than inheriting from them. This is also known as the "has-a" relationship, in contrast to inheritance, which is an "is-a" relationship.
Composition allows for more flexible and modular designs, since it promotes code reuse without the constraints of inheritance.
Here’s an example of object composition in PHP:
class Engine {
public function start() {
return "Engine started.";
}
}
class Wheels {
public function roll() {
return "Wheels are rolling.";
}
}
class Car {
private $engine;
private $wheels;
// Constructor to initialize the engine and wheels
public function __construct() {
$this->engine = new Engine();
$this->wheels = new Wheels();
}
// Method to start the car
public function start() {
return $this->engine->start() . " " . $this->wheels->roll();
}
}
// Creating a Car object
$car = new Car();
// Starting the car
echo $car->start(); // Outputs: Engine started. Wheels are rolling.
In this example:
- The Car class is composed of Engine and Wheels objects.
- The Car class uses these objects to perform its functionality, demonstrating composition.
Abstraction
Abstraction is the principle of hiding the complex implementation details of a system and exposing only the necessary and relevant parts. It simplifies the interaction with objects by providing a clear and simplified interface.
In PHP, abstraction is often implemented using abstract classes and interfaces, which I have explained above.
Combined with object composition, abstraction can help create more modular, reusable, and maintainable code. Composition allows you to build complex systems from simpler components, while abstraction simplifies the interface and interaction with those systems.
Value Objects
A value object represents a simple entity whose equality is not based on identity, but rather on the values it holds. They are immutable by design, meaning their state cannot be modified after creation. Value objects are often used to represent concepts like money, dates, or coordinates.
Here’s an example of a value object in PHP:
class Money {
private $amount;
private $currency;
public function __construct($amount, $currency) {
$this->amount = $amount;
$this->currency = $currency;
}
// Get the amount
public function getAmount() {
return $this->amount;
}
// Get the currency
public function getCurrency() {
return $this->currency;
}
// Add two Money objects
public function add(Money $other) {
if ($this->currency !== $other->currency) {
throw new InvalidArgumentException('Currencies do not match');
}
return new Money($this->amount + $other->amount, $this->currency);
}
// Check equality
public function equals(Money $other) {
return $this->amount === $other->amount && $this->currency === $other->currency;
}
}
// Example usage
$money1 = new Money(100, 'USD');
$money2 = new Money(200, 'USD');
$money3 = $money1->add($money2);
echo $money3->getAmount(); // Outputs: 300
echo $money3->getCurrency(); // Outputs: USD
echo $money1->equals($money2); // Outputs: (false)
In this example:
- The Money class is a value object.
- Instances of Money are immutable; once created, the amount and currency cannot be changed.
- The add method returns a new Money object, rather than modifying the existing one.
Mutability
Mutability refers to the ability of an object to be modified after it is created. While value objects are typically immutable, mutable objects can have their state changed.
Here’s an example of a mutable object in PHP:
class Person {
private $name;
private $age;
public function __construct($name, $age) {
$this->name = $name;
$this->age = $age;
}
// Get the name
public function getName() {
return $this->name;
}
// Set the name
public function setName($name) {
$this->name = $name;
}
// Get the age
public function getAge() {
return $this->age;
}
// Set the age
public function setAge($age) {
$this->age = $age;
}
}
// Example usage
$person = new Person('John', 30);
echo $person->getName(); // Outputs: John
echo $person->getAge(); // Outputs: 30
$person->setName('Jane');
$person->setAge(25);
echo $person->getName(); // Outputs: Jane
echo $person->getAge(); // Outputs: 25
In this example:
- The Person class is a mutable object.
- The setName and setAge methods allow modification of the object's state after it is created.
Using value objects promotes immutability and helps prevent unintended side effects, while mutable objects provide flexibility when changes to the object's state are necessary. Both concepts are crucial in designing robust and maintainable software systems.
Exceptions
Exceptions are a way to handle errors and other exceptional conditions in a program. Using exceptions, you can separate error-handling code from regular code, making your code cleaner and more maintainable. When an exceptional condition occurs, an exception is thrown, which can be caught and handled by a specific block of code.
Here’s a basic overview of how exceptions work in PHP:
- Throwing an Exception: You use the throw keyword to throw an exception.
- Catching an Exception: You use the try and catch blocks to catch and handle exceptions.
Example of Using Exceptions:
class CustomException extends Exception {}
function divide($dividend, $divisor) {
if ($divisor == 0) {
throw new CustomException("Division by zero.");
}
return $dividend / $divisor;
}
try {
echo divide(10, 2); // Outputs: 5
echo divide(10, 0); // This will throw an exception
} catch (CustomException $e) {
echo 'Caught exception: ', $e->getMessage(), "\n"; // Outputs: Caught exception: Division by zero.
} finally {
echo "Finally block executed."; // Always executed
}
In this example:
- The CustomException class extends the built-in Exception class, allowing you to create your own custom exceptions.
- The divide function checks if the divisor is zero and throws a CustomException if it is.
- The
try
block attempts to execute code that may throw an exception. - The
catch
block catches the exception and handles it, printing an error message. - The
finally
block contains code that will always be executed, regardless of whether an exception was thrown or not.
Using exceptions effectively can make your code more robust, easier to debug, and maintain, by clearly separating error-handling logic from regular business logic.