Introduction to Object-Oriented Programming (OOP) in PHP

Introduction to Object-Oriented Programming (OOP) in PHP

Transform Your PHP Code with the Power of Object-Oriented Programming

ยท

8 min read

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

๐Ÿ’ก
In this post, we'll quickly go over how to create a class to understand how it can improve our code. In the next part of this series, we will take a look at this again and go over it in detail, step by step.

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.

๐Ÿ‘‰
You can find the next posts of this series here: https://mastering-php.com/

Did you find this article valuable?

Support Linus Benkner by becoming a sponsor. Any amount is appreciated!

ย