Functions & OOP in PHP

Typed functions, classes, constructor property promotion, interfaces, and traits.

Functions with typed parameters and return types

Modern PHP lets you declare types for parameters and return values — the engine enforces them at runtime (a TypeError is thrown on mismatch), which catches a large class of bugs that used to only surface much later.

PHP
<?php

function calculateTotal(float $price, int $quantity, float $taxRate = 0.0): float {
    $subtotal = $price * $quantity;
    return $subtotal + ($subtotal * $taxRate);
}

echo calculateTotal(19.99, 3);        // 59.97
echo calculateTotal(19.99, 3, 0.1);    // 65.967
PHP
<?php

function findUser(int $id): ?string { // ?string means "string or null" (a nullable return type)
    $users = [1 => "Ada", 2 => "Grace"];
    return $users[$id] ?? null;
}

var_dump(findUser(1)); // string(3) "Ada"
var_dump(findUser(99)); // NULL

Passing the wrong type where PHP can't coerce it throws immediately:

PHP
<?php

function greet(string $name): string {
    return "Hello, $name!";
}

greet(42);       // fine — PHP coerces int to string in non-strict mode
declare(strict_types=1); // placed at the top of a file, this disables that coercion

Adding declare(strict_types=1); as the very first line of a file disables PHP's automatic type coercion for that file's function calls — passing an int where a string is declared then throws a TypeError instead of silently converting. Most modern, professional PHP codebases enable this everywhere.

Classes

PHP
<?php

class BankAccount {
    private float $balance;

    public function __construct(float $initialBalance = 0.0) {
        $this->balance = $initialBalance;
    }

    public function deposit(float $amount): void {
        if ($amount <= 0) {
            throw new InvalidArgumentException("Amount must be positive");
        }
        $this->balance += $amount;
    }

    public function getBalance(): float {
        return $this->balance;
    }
}

$account = new BankAccount(100.0);
$account->deposit(50.0);
echo $account->getBalance(); // 150

Constructor property promotion (PHP 8+)

Instead of declaring a property, then a constructor parameter, then manually assigning one to the other, PHP 8 lets you do all three in one place:

PHP
<?php

// Before PHP 8 — three separate steps
class Employee {
    private string $name;
    private float $salary;

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

// PHP 8+ — constructor property promotion collapses all three into the signature
class Employee {
    public function __construct(
        private string $name,
        private float $salary,
    ) {}
}

$employee = new Employee("Ada", 85000.0);

Adding a visibility modifier (private, protected, or public) directly on a constructor parameter is what triggers promotion — PHP declares the property and assigns it automatically, with no body needed at all if there's nothing else to do.

Interfaces

An interface defines a contract — method signatures a class must implement, with no implementation of its own. A class can implement multiple interfaces, even though PHP (like Java) only allows extending one parent class.

PHP
<?php

interface Payable {
    public function calculatePay(): float;
}

class HourlyEmployee implements Payable {
    public function __construct(
        private float $hoursWorked,
        private float $hourlyRate,
    ) {}

    public function calculatePay(): float {
        return $this->hoursWorked * $this->hourlyRate;
    }
}

function printPaycheck(Payable $employee): void { // works with ANY Payable implementation
    echo $employee->calculatePay();
}

printPaycheck(new HourlyEmployee(40, 25.0)); // 1000

Traits

A trait is a chunk of reusable method implementations you can mix into multiple, otherwise unrelated classes — PHP's answer to not having multiple inheritance, since a class can only extends one parent but can use many traits.

PHP
<?php

trait Loggable {
    public function log(string $message): void {
        echo "[" . static::class . "] " . $message . "\n";
    }
}

trait Timestamped {
    private ?DateTimeImmutable $createdAt = null;

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

class Order {
    use Loggable, Timestamped; // mix in behavior from both traits
}

$order = new Order();
$order->log("Order placed");   // [Order] Order placed
$order->markCreated();

If two traits used in the same class define a method with the same name, PHP raises a fatal error unless you explicitly resolve the conflict with insteadof/as — this is rare in practice, but worth knowing exists:

PHP
<?php

trait A {
    public function hello(): string { return "Hello from A"; }
}
trait B {
    public function hello(): string { return "Hello from B"; }
}

class Greeter {
    use A, B {
        A::hello insteadof B; // explicitly pick A's version for the conflicting method
        B::hello as helloFromB; // and still expose B's version under a new name
    }
}

Common mistakes

  • Forgetting declare(strict_types=1); and being surprised when a wrong-typed argument gets silently coerced instead of raising a TypeError.
  • Reaching for a trait to share state and behavior across classes that aren't really related at all — a trait should model "this class has this reusable capability," not a substitute for a shared parent class or a proper collaborator object.
  • Forgetting an interface only declares method signatures — it can't hold implementation or (traditionally) any state; that's what a trait or abstract class is for.

Interview questions

Q: What's the difference between a trait and an interface? An interface only declares a contract (method signatures) with no implementation — a class implementing it must provide its own code for every method. A trait provides actual, reusable method implementations that get copied into any class that uses it, which is how PHP works around not supporting multiple class inheritance.

Q: What does constructor property promotion do? It lets you declare a class property, a constructor parameter, and the assignment of one to the other in a single line, by adding a visibility modifier directly to a constructor parameter — PHP generates the property declaration and the $this->x = $x assignment automatically, cutting out a lot of repetitive boilerplate for simple data-holding classes.