PHP Development

Demystifying PHP: A Comprehensive Exploration of Object-Oriented Programming

By the Domain India teamPublished 9 min read
Knowledge base article
Contents (8 sections)

Object-oriented programming (OOP) is how most modern PHP is written: WordPress plugins, Laravel and Symfony applications, payment SDKs and most libraries you install are built from classes and objects. Once you understand a handful of ideas, that code stops looking like magic. This guide explains those ideas with short, tested PHP 8 examples, and shows which newer features you can use on your hosting.

Key takeaways

A class is a blueprint; an object is one thing built from it, with its own data (properties) and behaviour (methods). Use visibility (public, protected, private) to protect data, constructor promotion and readonly to keep objects valid, interfaces to define what a class must do, and inheritance, abstract classes and traits to share code. Enums (PHP 8.1) replace magic strings. Prefer small classes that receive what they need (composition) over deep inheritance chains.

1. Classes, objects and why OOP helps

A class describes a kind of thing. An object is one instance of it. Each object keeps its own property values, and its methods work on that data through $this.

OOP earns its place in larger code for three reasons:

Encapsulation
An object guards its own data. Other code can only change it through methods that check the change is valid.
Reuse
Shared behaviour lives in one place: a parent class, a trait or a small helper object.
Swappable parts
Code that depends on an interface works with any class that implements it: a UPI gateway today, a card gateway tomorrow.

For a short script that reads a form and sends one email, plain functions are fine. OOP pays off when the code grows, has several people working on it, or needs tests.

2. Properties, methods, constructors and visibility

Modern PHP lets you declare and assign properties directly in the constructor. This is called constructor promotion (PHP 8.0):

php
<?php
declare(strict_types=1);

final class Customer
{
    public function __construct(
        public readonly string $name,
        private string $email,
    ) {}

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

    public function changeEmail(string $email): void
    {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException("Invalid email: $email");
        }
        $this->email = $email;
    }
}

$c = new Customer('Asha Traders', '[email protected]');
echo $c->name;                         // Asha Traders
$c->changeEmail('[email protected]');
echo $c->email();                      // [email protected]
$c->name = 'X';                        // Error: Cannot modify readonly property

The three visibility keywords decide who can touch a property or method:

KeywordAccessible fromUse it for
publicAnywhereThe methods other code is meant to call
protectedThe class and its child classesHelpers that subclasses may need
privateOnly the class itselfInternal data and implementation details

Make properties private by default and expose methods. The changeEmail() method above can refuse a bad address; a public $email property could not. A readonly property (PHP 8.1) can be set once, inside the class, and never changed afterwards.

Always add declare(strict_types=1); at the top of the file and type every property, parameter and return value. PHP then rejects wrong types instead of silently converting them.

3. Inheritance and overriding

A child class extends a parent, gets its public and protected members, and can override methods. Call the parent's version with parent:::

php
<?php
class Employee
{
    public function __construct(
        protected string $name,
        protected int $monthlySalary,
    ) {}

    public function describe(): string
    {
        return "{$this->name} earns ₹{$this->monthlySalary} a month";
    }
}

class Manager extends Employee
{
    public function __construct(string $name, int $monthlySalary, private int $teamSize)
    {
        parent::__construct($name, $monthlySalary);
    }

    public function describe(): string
    {
        return parent::describe() . " and manages {$this->teamSize} people";
    }
}

foreach ([new Employee('Ravi', 30000), new Manager('Meera', 60000, 5)] as $person) {
    echo $person->describe(), PHP_EOL;
}
// Ravi earns ₹30000 a month
// Meera earns ₹60000 a month and manages 5 people

The loop calls describe() without knowing which class it has. That is polymorphism: one call, different behaviour depending on the object.

Mark a class final when it isn't designed to be extended. Deep inheritance trees (a class extending a class extending a class) become hard to change, so keep them shallow.

4. Interfaces and abstract classes

An interface lists methods a class promises to have, with no code. An abstract class can hold shared code and leave some methods for children to fill in. It can't be created with new on its own.

php
<?php
interface PaymentGateway
{
    public function charge(int $paise, string $orderId): string;
}

abstract class BaseGateway implements PaymentGateway
{
    abstract protected function prefix(): string;

    public function charge(int $paise, string $orderId): string
    {
        return sprintf('%s-%s charged ₹%s', $this->prefix(), $orderId, number_format($paise / 100, 2));
    }
}

final class UpiGateway extends BaseGateway
{
    protected function prefix(): string { return 'UPI'; }
}

final class CardGateway extends BaseGateway
{
    protected function prefix(): string { return 'CARD'; }
}

function checkout(PaymentGateway $gateway, int $paise, string $orderId): void
{
    echo $gateway->charge($paise, $orderId), PHP_EOL;
}

checkout(new UpiGateway(), 11800, 'A1001');   // UPI-A1001 charged ₹118.00
checkout(new CardGateway(), 59900, 'A1002');  // CARD-A1002 charged ₹599.00

checkout() depends only on the interface. Adding a new gateway means writing one new class, not editing the checkout code. Store money as whole paise (integers), never as floats.

5. Traits, static members and enums

Traits copy a set of methods into several unrelated classes. Use them for small, self-contained behaviour such as timestamps, not as a substitute for proper design:

php
<?php
trait Timestamps
{
    private ?DateTimeImmutable $createdAt = null;

    public function touch(): void
    {
        $this->createdAt ??= new DateTimeImmutable();
    }

    public function createdAt(): ?DateTimeImmutable
    {
        return $this->createdAt;
    }
}

final class Invoice
{
    use Timestamps;
}

Static properties and methods belong to the class, not to an object (Counter::next()). Use them sparingly: static state is shared by all code and makes testing harder.

Enums (PHP 8.1) give a fixed set of named values instead of loose strings, and can carry methods:

php
<?php
enum OrderStatus: string
{
    case Pending = 'pending';
    case Paid = 'paid';
    case Cancelled = 'cancelled';

    public function label(): string
    {
        return match ($this) {
            self::Pending => 'Awaiting payment',
            self::Paid => 'Paid',
            self::Cancelled => 'Cancelled',
        };
    }
}

echo OrderStatus::from('paid')->label();         // Paid
var_dump(OrderStatus::tryFrom('refunded'));       // NULL

from() throws on an unknown value; tryFrom() returns null, which is safer for user input.

6. Composition, destructors and newer features

Composition means an object receives the objects it needs, usually through its constructor, instead of inheriting from them. This is also called dependency injection:

php
<?php
final class OrderService
{
    public function __construct(private Mailer $mailer) {}

    public function confirm(string $email, string $orderId): void
    {
        $this->mailer->send($email, "Order $orderId confirmed");
    }
}

In a test you can pass a fake Mailer. In production you pass the real one. Most frameworks wire this up for you.

Destructors (__destruct()) run when the last reference to an object disappears, for example when a function that created it returns, or at the end of the request. They suit clean-up such as closing a handle. Don't rely on them for important work like sending email, because the timing isn't always obvious.

PHP 8.4 added property hooks and asymmetric visibility, which cut down on getter and setter boilerplate:

php
<?php
final class Product
{
    public string $name {
        set(string $value) => trim($value);
    }

    public private(set) int $pricePaise = 0;
}

Here $name is trimmed whenever it is set, and $pricePaise can be read from anywhere but changed only inside the class. These need PHP 8.4 or later; on older versions, use a private property with a method.

Autoloading: one class per file

Put each class in its own file, named after the class, in folders that match its namespace (the PSR-4 standard). Composer then generates an autoloader, so you never write require for a class again. Run Composer on your computer or in CI and upload the vendor/ folder with your project.

7. Common mistakes

Public properties everywhere
Any code can put the object into an invalid state. Use private properties and methods that check input.
God classes
One class that handles the database, email and HTML is hard to change. Split it by responsibility.
Inheritance for code reuse only
If a Manager is not really a kind of Mailer, don't extend Mailer. Pass a Mailer in.
Untyped code
Missing types hide bugs until production. Type everything and use strict_types.

For how classes fit together in a real application, see the MVC pattern in PHP. For the other paradigms, see functional programming in PHP.

8. Running this on Domain India

Everything in this guide is plain PHP and runs on Domain India shared hosting. What matters is the PHP version selected for your account. We ran every example above on our cPanel server with PHP 8.5, and all except the property-hook example on PHP 8.3.

  • cPanel: PHP versions up to 8.5 are selectable per account, and new accounts default to PHP 8.3. Property hooks and asymmetric visibility need 8.4 or later. See how to change your PHP version.
  • DirectAdmin: the newest version offered is PHP 8.3, so sections 1 to 5 work as written; use private properties and methods instead of property hooks.
  • Composer: it isn't pre-installed, but over jailed SSH you can run php composer.phar install, adding --no-scripts if it stops with a proc_open message. Or build locally and upload vendor/. See PHP disabled functions on shared hosting.

For the full list of versions, see software versions on Domain India hosting.

cPanel Starter
₹125/mo + GST
  • 25 GB NVMe SSD Storage
  • 50 GB Monthly Bandwidth
  • 1 Website
  • 10 Email Accounts
See plan details

The price on the card is a live Domain India list price and excludes 18% GST.

What is the difference between a class and an object in PHP?

A class is the blueprint that defines properties and methods. An object is one instance created from the class with the new keyword. You can create many objects from one class, and each keeps its own property values.

What do public, protected and private mean in PHP?

They control access. Public members can be used from anywhere, protected members from the class and its child classes, and private members only from inside the class itself. Make properties private by default.

When should I use an interface instead of an abstract class?

Use an interface to describe what a class must be able to do, with no shared code. Use an abstract class when several related classes share real code and differ only in a few methods. A class can implement many interfaces but extend only one class.

What is constructor property promotion?

Since PHP 8.0 you can declare a property by adding a visibility keyword to a constructor parameter, for example public function __construct(private string $email) {}. PHP creates and assigns the property for you.

Which PHP version do I need for enums and readonly properties?

Enums and readonly properties need PHP 8.1 or later. Readonly classes need PHP 8.2, and property hooks and asymmetric visibility need PHP 8.4.

Is OOP slower than procedural PHP?

Not in any way that matters for a normal website. With OPcache the difference is negligible, and the time saved in maintenance and testing is far larger.

Ready to try it? Check the PHP version on your account in your control panel, then read the MVC guide next, or compare plans on cPanel hosting.

Host modern PHP

Choose your PHP version per account, up to PHP 8.5 on our cPanel servers, with free SSL and weekly backups.

See cPanel plans

Was this article helpful?

Your answer helps us decide what to improve next.

Still need help? Open a support ticket and our team will reply.

Prefer an app? Add this site to your home screen.Get the app
Object-Oriented Programming in PHP 8 | Domain India