Introduction to Object-Oriented Programming (OOP) in PHP
Transform Your PHP Code with the Power of Object-Oriented Programming
Hey!
This is the first of a series where we transform simple scripts into powerful, modular applications! If you've been coding in PHP but haven't explored the magic of Object-Oriented Programming (OOP) yet, you're in for a treat. Starting with the basics (what is a class, anyway?) we will make our way up to more advanced topics like dependency injection, service containers, and different design patterns.
We'll start by breaking down the fundamental concepts of OOP โ what it is, why it matters, and how it can revolutionize your approach to coding. Through simple, hands-on examples, you'll see how classes and objects form the backbone of this paradigm, setting the stage for more complex and exciting topics in our upcoming posts.
Ready to unlock a new level of coding proficiency? Let's dive into the basics of OOP in PHP and discover how to build better, cleaner, and more scalable applications from the ground up!
What is Object-Oriented Programming (OOP)?
In OOP, computer programs are designed by making them out of objects that interact with one another. โ Wikipedia
In the OOP world, you're mostly working with classes. You have one class that interacts with another class, which interacts with another class, and so on.
But before we go too deep into the concepts behind OOP, let's start with some code:
<?php
$user = "John Doe";
$balance = 100;
function deposit(int $amount) {
global $balance;
$balance += $amount;
}
function withdraw(int $amount) {
global $balance;
$balance -= $amount;
}
// Usage
deposit(500);
withdraw(200);
echo "Balance: $" . $balance;
// Balance: $400
As you can see, we define a user, a balance, and two functions to modify it.
Tip: You can play around with the examples in this post on the online php playground!
This works for now, but there's a good chance we'll run into issues with this. For example, this only works with one $balance
variable. What if we have a transaction and want to withdraw from one account and deposit to another?
Writing the First Class
Now, let's rebuild it with OOP in mind. To get started, we can think about the main objects we are working with. In this example, that's the user. So, let's create a user class!
But what is a class, anyway? A class is an object that has properties (attributes) and methods (functions). And, you can have many instances of one class.
In PHP, we define a class using the class keyword followed by a name. The name should be in PascalCase:
class User {
}
Cool! We can now create new instances of that class using the new
keyword:
$user1 = new User;
var_dump($user1);
$user2 = new User;
var_dump($user2);
The result looks like this:
object(User)#1 (0) {
}
object(User)#2 (0) {
}
The different IDs behind the class name indicate that both objects are different instances of that class. If you var_dump
the $user1
variable twice, the ID will be the same, as it's the same instance:
$user1 = new User;
var_dump($user1); // object(User)#1 (0) { }
var_dump($user1); // object(User)#1 (0) { }
Adding Properties
But our class is still empty, so let's change that. We can define properties directly inside the class:
class User {
public string $name = "John Doe";
public int $balance = 100;
}
Note: We'll come back later topublic
. For now, just remember that public means it can be accessed from outside the class.
If we var_dump
our two user instances again, we can see the following output:
object(User)#1 (2) {
["name"]=> string(8) "John Doe"
["balance"]=> int(100)
}
object(User)#2 (2) {
["name"]=> string(8) "John Doe"
["balance"]=> int(100)
}
The Class Constructor
Both instances have the same values since we hardcoded them in the class. Let's change that and instead assign them when creating an instance. To do this, we need to add a class constructor. The constructor is a method that is called when creating a new instance of the class, and it can take parameters just like normal functions:
class User {
public string $name;
public int $balance;
public function __construct(string $name, int $balance) {
}
}
Now, the only thing missing is to assign the parameters received from the constructor to the class properties. We can reference properties and methods inside the class using the this
keyword:
class User {
public string $name;
public int $balance;
public function __construct(string $name, int $balance) {
$this->name = $name;
$this->balance = $balance;
}
}
Now, we can pass in the data when creating instances of the User class:
$user1 = new User("John Doe", 100);
var_dump($user1);
$user2 = new User("Jane Doe", 200);
var_dump($user2);
This results in the following output:
object(User)#1 (2) {
["name"]=> string(8) "John Doe"
["balance"]=> int(100)
}
object(User)#2 (2) {
["name"]=> string(8) "Jane Doe"
["balance"]=> int(200)
}
Using Class Properties
We can also use the class properties:
$user1->balance += 300;
echo "Balance of " . $user1->name . ": " . $user1->balance;
// Balance of John Doe: 400
echo "Balance of " . $user2->name . ": " . $user2->balance;
// Balance of Jane Doe: 200
Adding Methods
To fully convert our previous example to a class, we also need to add two methods to deposit and withdraw some amount from the balance. We can do so by adding public functions to the class (and functions inside a class are called methods).
These methods can also reference properties and other methods in the class using $this
:
class User {
public string $name;
public int $balance;
public function __construct(string $name, int $balance) {
$this->name = $name;
$this->balance = $balance;
}
public function deposit(int $amount) {
$this->balance += $amount;
}
public function withdraw(int $amount) {
$this->balance -= $amount;
}
}
And we can use these methods like this:
$user1 = new User("John Doe", 100);
$user1->deposit(500);
echo $user1->name . "'s balance: " . $user1->balance;
// John Doe's balance: 600
$user2 = new User("Jane Doe", 200);
$user2->withdraw(100);
echo $user2->name . "'s balance: " . $user2->balance;
// Jane Doe's balance: 100
To wrap it up, we converted the following code:
<?php
$user = "John Doe";
$balance = 100;
function deposit(int $amount) {
global $balance;
$balance += $amount;
}
function withdraw(int $amount) {
global $balance;
$balance -= $amount;
}
// Usage
deposit(500);
withdraw(200);
echo "Balance: $" . $balance;
// Balance: $400
To this:
<?php
class User {
public string $name;
public int $balance;
public function __construct(string $name, int $balance) {
$this->name = $name;
$this->balance = $balance;
}
public function deposit(int $amount) {
$this->balance += $amount;
}
public function withdraw(int $amount) {
$this->balance -= $amount;
}
}
$user = new User("John Doe", 100);
$user->deposit(500);
$user->withdraw(200);
echo "Balance: $" . $user->balance;
// Balance: $400
Interact with Another Class
But with the introduction of the user class, we are no longer limited to a global balance variable and one user but instead can create multiple users where each user has their own balance.
We can also extend this by allowing one user to pay another user:
class User {
...
public function pay(User $payee, int $amount) {
if($this->balance < $amount) {
throw new Exception("Insufficient funds!");
}
$this->withdraw($amount);
$payee->deposit($amount);
}
...
}
// Usage
$user = new User("John Doe", 100);
$user2 = new User("Jane Doe", 200);
$user->pay($user2, 50);
echo "Balance of " . $user->name . ": $" . $user->balance;
// Balance of John Doe: $50
echo "Balance of " . $user2->name . ": $" . $user2->balance;
// Balance of Jane Doe: $250
Benefits of Using OOP in PHP
What's great about our new User class is the grouping of all related functionality inside this one class. There are no global variables, no functions spread across the codebase, and all data and functions are inside the class.
If you are using the User class and want to know what you can do with it, you can find all available methods in this one place.
As a bonus, most IDEs can show you the available methods while typing a variable that is a User instance:
Also, with this class structure, our application is very modular. Imagine your database connection, logger, and cache separate classes. For example, if you need to change the logger to use Redis, you only need to change the logger class, because all logic related to the cache is stored right in there.
And if you need to create for example an admin user type, you can create a new class that extends the user class and just adds the functionality for the admin. No need to re-implement the user functionality, it's inherited from the base class (in this case the user class).
But don't worry if you are not familiar with stuff like inheritance, we'll get to that!
Next Steps
If this was your first time diving into the world of OOP, this was a lot of stuff! Play around with the examples in this post, add some methods, and maybe even create another class!
When you're ready, head over to the next post.