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 Car
s, 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
orself
)protected
- Only the class that defines it and classes inheriting this class may access this property/call this methodpublic
- 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.