Deep Dive into Classes and Objects in PHP

Deep Dive into Classes and Objects in PHP

Understanding Classes in PHP for Better Code Structure

Never used a class before? Read the introduction to OOP in PHP first.

Hey again!

In the last post, we created our first class and discovered some benefits of using OOP in PHP. However, in this post, we'll dive deeper into classes and what we can do with them. So, let's get to work!

Class Properties and Methods

We'll use the following class as starting point:

<?php

class User {

}

Properties

Properties are variables attached to an instance of a class. For example, every user has a name. So, we can add a $name property to the class:

class User {
    public $name = "John";
}

We can access these properties using the arrow syntax:

$user = new User;
echo $user->name; // "John"

We can also modify the property:

$user = new User;
$user->name = "John Doe";
echo $user->name; // "John Doe"

Just like arguments of functions, we can also add type declarations to our properties to ensure type safety:

<?php
declare(strict_types=1);

class User {
    public string $name = "John";
}

Now, if we assign a different type than we specified in the class, we will get an error:

$user = new User;
$user->name = 10;
// Fatal error: Uncaught TypeError:
// Cannot assign int to property User::$name of type string

We can also define a property without initialising it:

class User {
    public string $name;
}

A common use case is to define properties and then initialise them with values in the class constructor.

Methods

Methods are functions attached to an instance of a class. Inside a method, we have access to the current instance of the class and can access and modify the properties and call other methods.

class User {
    public string $name = "John";

    public function greet() {
        echo "Hello!";
    }
}

$user = new User;
$user->greet(); // "Hello!"

The variable $this references the current instance of the class. So if we want to use the user's name in the greeting, we can do so by referencing the $this->name:

class User {
    public string $name = "John";

    public function greet() {
        echo "Hello " . $this->name . "!";
    }
}

$user = new User;
$user->greet(); // "Hello John!"

Like normal functions, methods can also have arguments, type-hinted arguments, and return types:

class User {
    public string $name = "John";

    public function greet(): void {
        echo "Hello " . $this->name . "!";
    }

    public function sayHello(string $name): string {
        return "Hello $name";
    }
}

$user = new User;
$user->greet(); // "Hello John!"

$hello = $user->sayHello("Linus");
echo $hello; // "Hello Linus";

The Class Constructor

The constructor is a magic method that gets executed every time a new instance of that class is constructed. For example:

class User {
    public function __construct() {
        echo "New user created!";
    }
}

$user = new User; // "New user created!"

We can also add arguments to the constructor method which then need to be passed when creating a new instance:

class User {
    public function __construct(string $name) {
        echo "New user created: " . $name;
    }
}

$user = new User("John"); // "New user created: John"

But for now, the $name variable is only available in the constructor. But we can define a property on the class and initialise it in the constructor:

class User {
    public string $name;

    public function __construct(string $name) {
        $this->name = $name;
    }
}

$user = new User("John");
echo $user->name; // "John"

Note that $name refers to the value passed to the constructor, while $this->name refers to the property $name on the class.

PHP 8: Constructor Property Promotion

The constructor is commonly used to initialise class properties, just like we did above. However, this requires a bunch of code just to assign a variable. Take a look at this:

class User {
    public string $firstname;
    public string $lastname;
    public bool $admin;

    public function __construct(string $firstname, string $lastname, bool $admin) {
        $this->firstname = $firstname;
        $this->lastname = $lastname;
        $this->admin = $admin;
    }
}

Thankfully, PHP 8 introduced us to constructor property promotion: This allows us to refactor the code above like this:

class User {
    public function __construct(
        public string $firstname,
        public string $lastname,
        public bool $admin,
    ) {
        //
    }
}

Very cool!

The Class Destructor

There is a constructor that runs when constructing a new instance, so there also is a destructor that runs when an instance is destroyed.

class User {
    public function __construct(
        public string $name,
    ) {
        echo "Constructed " . $this->name;
    }

    public function __destruct() {
        echo "Destructing " . $this->name;
    }
}

$user = new User("John");
// "Constructed John"

unset($user);
// "Destructing John"

More Magic Methods

There are a lot more magic methods that we haven't covered yet, like clone, get, set, and more.

If you stumble over a method starting with "__" (two underscores), you just found a magic method. They are reserved to PHP (so don't create your own), but you can implement them, just like we did with the constructor and destructor.

Visibility: Public, Private, and Protected

You probably have noticed that we added a public in front of all properties and methods we added to our classes to far. But what does it actually mean?

public, private, and protected define the visibility, or in other words, where the property/method can be accessed from:

Public

Public means just what it says: It's public, so you can access it from both inside and outside the class:

class User {
    public string $name = "John";

    public function __construct() {
        $this->doSomething(); // ✅ Works (call method from inside the class)
    }

    public function doSomething(): void {
        $name = $this->name; // ✅ Works (use property from inside the class)
    }
}

$user = new User;
echo $user->name; // ✅ Works (use property from outside the class)

echo $user->doSomething(); // ✅ Works (call method from outside the class)

Private

Private means the opposite: It can only be accessed from inside the class and not the outside:

class User {
    private string $name = "John";

    public function __construct() {
        $this->doSomething(); // ✅ Works (call method from inside the class)
    }

    private function doSomething(): void {
        $name = $this->name; // ✅ Works (use property from inside the class)
    }
}

$user = new User;
echo $user->name; // ❌ Fails (use property from outside the class)

echo $user->doSomething(); // ❌ Fails (call method from outside the class)

Protected

Just like private, protected means you can't access the property/method from outside the class, but you can from the inside.

To understand the difference, we need an example with a class that extends another class:

class User {
    public string $name "John";
    private string $password = "12345";
    protected int $balance = 200;
}

class SuperUser extends User {
    public function doSomething() {
        $name = $this->name; // ✅ Works
        $password = $this->password; // ❌ Fails
        $balance = $this->balance; // ✅ Works
    }
}

$superuser = new SuperUser;
$name = $superuser->name; // ✅ Works
$password = $superuser->password; // ❌ Fails
$balance = $superuser->balance; // ❌ Fails

As you might can see, the difference lies in the visibility to the subclass. If the User class defines a property that is protected, it can't be accessed from the outside, but a class that extends the User class can access it.

If a property is private, it is only accessible from inside that class, and cannot be accessed from the outside or a class that extends it.

Don't worry if you don't yet understand how extends work, we'll take about that later in this series!

VisibilityVisible to own classVisible to subclassVisible to the outside
public
protected
private

Note: If you don't define the visibility, it will automatically be public.

Getters and Setters

Now we know about properties, methods, and visibility, we can talk about getters and setters.

Getters and setters are functions that allow you to get and set something:

class User {
    private string $name;

    public function getName(): string {
        return $this->name;
    }

    public function setName(string $name): void {
        $this->name = $name;
    }
}

// Usage
$user = new User;
$user->setName("John");
echo "Hello " . $user->getName(); // Hello John

As you can see, since we now have two public methods to get and set the name, we can change the visibility of the $name property to private.

This in itself might not seem too helpful, but this enables us a few things:

Validation

We can add validation to the setter:

class User {
    private string $name;

    public function getName(): string {
        return $this->name;
    }

    public function setName(string $name): void {
        if(strlen($name) < 2) {
            throw new Exception("Name must be at least 2 characters long");
        }

        $this->name = $name;
    }
}

// Usage
$user = new User;
$user->setName("J"); // ❌ Exception: "Name must be at least 2 characters long"
$user->setName("John"); // ✅ Works

Hide Internal Representation of the Data

Let's say we have a first and last name. The getter can return the full name, while the data is actually stored in two different properties:

class User {
    private string $firstname;
    private string $lastname;

    public function setFirstName(string $firstname): void {
        $this->firstname = $firstname;
    }

    public function setLastName(string $lastname): void {
        $this->lastname = $lastname;
    }

    public function getFullName(): string {
        return $this->firstname . ' ' . $this->lastname;
    }
}

// Usage
$user = new User;
$user->setFirstName("John");
$user->setLastName("Doe");
$fullname = $user->getFullName(); // "John Doe"

Next Steps

To recap, in this post you have learned:

  • How to create a class

  • How to define and use properties and methods

  • What the constructor is (and the destructor)

  • The different visibilities (public, private, protected)

  • Getters and setters

In the next part of this series, we'll dive into inheritance and the extends keyword (you have already seen an example in this post!)

But feel free to play around with classes before continuing. Understanding how classes work is fundamental for this series.

Whenever you feel ready, continue to the next part about inheritance.

Did you find this article valuable?

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