Object oriented PHP cheat sheet

Table of contents

This article will go over the entire object oriented feature set as of PHP 8.2, from basics to advanced concepts. PHP as a programming language has come a long way, and the object oriented features are arguably one of the main selling points that kept it a viable choice for modern web development today.

Classes and Objects

Let's start with the essentials: A class is a blueprint for creating an object. You can think of it as a custom data type.

You declare a class using the class keyword:

class Car{}

A class can contain variables (aka "properties") and functions (aka "methods"):

class Car{
	var int $maxSpeed = 200;
	function start(){
		echo "Starting car";
	}
}

Class instances (aka objects)

To use a class, you create an instance of it using the new keyword (except static, see below):

$myCar = new Car();
$yourCar = new Car();

You can think of it as a data type: there can be many variables of type string, but only one string type. The same applies here: you could have many different variables of type Car, but only one class Car (aka type).

Creating a variable of type Car creates an instance of it (aka object). Instances are completely isolated from one another, so $myCar and $yourCar are both Cars, but their values are not connected in any way. Changing $myCar does not affect $yourCar and vice-versa.

Using object properties and methods

To access a property or method of an object, you use the -> operator:

$myCar->start();
echo $myCar->maxSpeed; // prints 200

You can also modify properties this way:

$myCar->maxSpeed = 250;

Note that you can freely attach new properties to objects this way, but it is not recommended as it may make your code harder to follow:

$myCar->horsePower = 150;

This creates a new property $horsePower for this car. Other Car objects aren't affected by this.

If you want to get an instance's variable from within a method of the object, you can access the current instance using the $this variable:

class Car{
	var int $maxSpeed = 200;
	function getMaxSpeed(): int{
		return $this->maxSpeed;
	}
}

Using $this will only refer to the object instance the method was called on:

$myCar = new Car();
$yourCar = new Car();
$yourCar->maxSpeed = 10;
echo $myCar->getMaxSpeed(); // prints 200
echo $yourCar->getMaxSpeed(); // prints 10

As you can see, the function correctly uses the changed value on the object it was changed for ($yourCar), but not on the other ($myCar).

Inheritance

A class may inherit properties and methods from another class, which will then be called it's parent class. This is done using the extends keyword:

class Vehicle{
	function drive(){
		echo "Driving vehicle";
	}
}
class Car extends Vehicle{
	var int $maxSpeed = 200;
}

In this example, Car extends Vehicle, thus inherits all of it's properties and methods. Because of this, all Car instances will now also have a drive() method:

$myCar = new Car();
$myCar->drive(); // prints "Driving vehicle"

A child class may override (replace) a method or property it inherited from it's parent class by defining it itself:

class Vehicle{
	function drive(){
		echo "Driving vehicle";
	}
}
class Car extends Vehicle{
	var int $maxSpeed = 200;
	function drive(){
		echo "Driving car";
	}
}

Since Car defined the method drive() itself, it will use this instead of the method inherited from Vehicle:

$myCar = new Car();
$myCar->drive(); // prints "Driving car"

Although Car has it's own drive() method, it can still access the parent's drive method through the parent:: prefix:

class Vehicle{
	function drive(){
		echo "Driving vehicle";
	}
}
class Car extends Vehicle{
	var int $maxSpeed = 200;
	function drive(){
		echo "Driving car";
	}
	function driveVehicle(){
		parent::drive();
	}
}

By calling parent::drive(), the Vehicle->drive() method is called despite Car having a different method with the same name:

$myCar = new Car();
$myCar->drive(); // prints "Driving car"
$myCar->driveVehicle(); // prints "Driving vehicle"

Abstract and final

To get a little more control over inheritance, you can set rules for if and how inheritance may occur. The first one is the final keyword, disallowing overrides:

class Vehicle{
	final function drive(){
		echo "Driving vehicle";
	}
}

A child class like Car may still inherit from this class, but cannot define it's own drive() method anymore.

You can also mark an entire class as final:

final class Vehicle{
	function drive(){
		echo "Driving vehicle";
	}
}

A final class may not be inherited from at all (no class may extend it).

The second keyword is the polar opposite of this: abstract. If used on a method, it declares that it must be overridden by a child class:

abstract class Vehicle{
	abstract function drive();
}

You may have noticed that the drive() method does not have a function body. That is intentional: since this method must be overridden by a child class, it cannot have any logic itself.

Note that abstract methods may only be defined in an abstract class. An abstract class can only be inherited from, you cannot create an instance of it directly.

abstract class Vehicle{
	abstract function drive();
}
$v = new Vehicle(); // not possible for abstract classes

Access modifiers

A class can define how it's properties and methods may be accessed using access modifiers. The available modifiers are:

  • private - Only the class that defines it itself may access this property/call this method (using $this or self)
  • protected - Only the class that defines it and classes inheriting this class may access this property/call this method
  • public - This property or method is publicly available

An example:

class Vehicle{
	protected int $id = 14;
	private string $engine;
}
class Car extends Vehicle{
	public function getID(){
		return $this->id;
	}
}

The property $engine is only available to methods of the Vehicle class itself. Methods from Car or instances of Vehicle or Car cannot read or modify it. The protected $id property is available within methods of both Vehicle and Car, but not to instances of it:

$myCar = new Car();
echo $myCar->getID(); // prints 14
echo $myCar->id; // throws error

The public method getID() of the Car class is the only part of all classes that can actually be used from an instance of the Car class.

Constructors and destructors

When an instance of a class is created, PHP will implicitly look for a constructor method named __construct(). We can use this method to set up initial values for properties or to demand that some data is given to the object during creation:

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

In this example, we defined that you need to provide a name when creating a Car, which is then stored in the $name property of the newly created object:

$myCar = new Car("Green pickup");
echo $myCar->name; // prints "Green pickup"

Since assigning properties from the constructor is so common, you can use a special parameter syntax called constructor property promotion to have it done for you implicitly. You use this by adding an access modifier before the variable's data type:

class Car{
    public function __construct(public string $name){}
}

This code functions the exact same way as the previous constructor, only that we did not have to manually set the property in the function body.

A destructor is a method named __destruct() that is called once there are no more references to an object (aka your code is not using it anymore, so it can be garbage-collected):

class Car{
	public function __destruct(){
		echo "Car drove away";
	}
}

There are no guarantees when an object might be destructed after it's last use, just that the destructor will be called before it is actually being destroyed:

$myCar = new Car("Green pickup");
echo "Done with the car";
// prints "Car drove away" here

Readonly properties

Sometimes you may want a property to not be altered after it has been set. Since PHP 8.1, you can use the readonly keyword for class properties to just that:

class Car{
    public readonly int $maxSpeed;
    public function setMaxSpeed(int $speed){
        $this->maxSpeed = $speed;
    }
}
$myCar = new Car();
$myCar->setMaxSpeed(200); // works, because it is the first value $maxSpeed gets
$myCar->setMaxSpeed(10); // throws error, because $maxSpeed already has a value and is readonly

Properties declared readonly may only receive a value once, and only from within their class (either during initialization or through a class method).

If you want to mark every property of a class as readonly, you can make the class readonly instead:

readonly class Car{
    public int $maxSpeed;
    public string $model;
    public string $make;
}

Now all three properties will be treated as readonly.

Interfaces

An interface defines a set of methods that a class is guaranteed to contain. You cannot create an instance of an interface, only implement it. By implementing an interface, you promise that your class contains all methods defined in the interface:

interface Drivable{
	public function drive(string $target): void;
}
class Car implements Drivable{
	public function drive(string $target): void{
		echo "Driving car to $target";
	}
}
class Bike implements Drivable{
	public function drive(string $target): void{
		echo "Riding bike to $target";
	}
}

Interfaces are often used to refer to several objects in a compatible way. Let's consider this code:

$vehicles = [new Car(), new Bike(), new Car()];
function park(Drivable $d){
    $d->drive("parking lot");
}

foreach($vehicles as $vehicle){
    park($vehicle);
}

The array $vehicles contains multiple different objects. The function park() specifies that it's parameter is a Drivable, so all objects implementing the Drivable interface are valid and the park() function has the guarantee that no matter what object is passed into it, it will have a drive() method.

Traits

Traits are like inverse abstract classes: they contain one or more methods but including the function body that any class can import:

trait Starter{
	public function start(): void{
		echo "Starting engine";
	}
}
class Car{
	use Starter;
}
$myCar = new Car();
$myCar->start(); // prints "Starting engine"

Traits can be used to share common logic with many classes or to override inherited parent methods.

Static methods and properties

Something marked as static will work outside of instances of this class. A static property or method can only be called on the class, not on instances of it. Static elements are accessed using the :: operator instead of -> :

class Car{
	public static $make = "Some brand";
}
$myCar = new Car();
echo $myCar->make; // throws error
echo Car::$make; // correct usage of static variable

A class may refer to a static element using the self:: prefix instead of $this-> :

class Car{
	private static $model = "Sample model";
	public function getModel(): string{
		return self::$model;
	}
}
$myCar = new Car();
echo $myCar->getModel(); // prints "Sample model"


A common use of static properties within classes is called the Singleton pattern, which will initialize a resource only once, then share the resource among new function calls:

class Car{
	private static bool $gateOpened = false;
	public function drive(string $target): void{
		if(!self::$gateOpened){
			echo "Opening gate. ";
			self::$gateOpened = true;
		}
		echo "Driving to $target";
	}
}

The static property $gateOpened is not part of any instances of Car, but behaves as a shared global between all instances of Car:

$myCar = new Car();
$yourCar = new Car();
$myCar->drive("work"); // prints "Opening gate. Driving to work"
$myCar->drive("parking lot"); // prints "Driving to parking lot"
$yourCar->drive("party"); // prints "Driving to party"

In this example, the gate is only opened once across all Car instances and the state is shared between all Car objects because the variable holding it is static. This is great for connections to databases/caches or to initialize files for logging.

More articles

Setting up a LAMP stack for development in docker

Streamlining your local PHP development environment

Responsible web scraping considerations

Web scraping within legal limits, explained for humans

Documenting HTTP APIs with OpenAPI

Make your API documentation more user-friendly and streamline the writing process

Choosing the right RAID setup

Making sense of pros and cons for RAID configurations

A gentle introduction to systemd

A broad overview of systemd components and features

Advanced Docker Compose features

Getting more out of container stacks