Advanced PHP OOP: 29 Questions Will Challenge You
In this article, I’ve collected some tricky and confusing points in PHP OOP that developers often face in job interviews. But the real goal isn’t just passing interviews — it’s about deeply understanding these concepts and how OOP works behind the scenes.
*Special thanks to my friend Abd-El Rahman Fathy for sharing his notes with me to support the content.
(1) What are Magic Methods in PHP?
In PHP, magic methods are special methods that start with double underscores (__) and are automatically triggered when certain actions happen to an object.
They are predefined by PHP and let you control object behavior beyond normal methods.
🗹 Common Magic Methods:
__construct()→ runs when an object is created.__destruct()→ runs when an object is destroyed.__get($name)→ triggered when accessing an undefined/inaccessible property.__set($name, $value)→ triggered when setting an undefined/inaccessible property.__isset($name)→ triggered whenisset()orempty()is used on inaccessible or non-existing property.__unset($name)→ triggered whenunset()is used on inaccessible (protected or private) or non-existing properties.__call($method, $args)→ triggered when invoking inaccessible methods in an object context__callStatic($method, $args)→ is triggered when invoking inaccessible methods in a static context.__toString()→ defines how an object is converted to a string.__invoke()→ triggered when an object is used as a function.__clone()→ runs when object is cloned.
(2) What is Constructor? What are their types?
A constructor in PHP is a special method (__construct) that runs automatically when you create an object from a class.
- Used to initialize properties or run setup code.
- Defined with
__construct().
🗹 Constructor Types
1- Default Constructor
- Automatically created if you don’t define one.
class A
{
}
$obj = new A(); // works even without __construct2- Parameterized Constructor
- Takes arguments when creating objects.
class B
{
public function __construct($name)
{
echo "Hello $name";
}
}
new B("John"); // Hello John3- Copy Constructor (simulated in PHP)
- PHP doesn’t have a true copy constructor like C++, but you can simulate it by passing an object to the constructor.
class C
{
public $x;
public function __construct($obj = null) {
if ($obj) $this->x = $obj->x;
}
}(3) Can a Constructor be private in PHP?
YES, In PHP, a constructor can be private.
Why?
- To restrict object creation from outside the class.
- Commonly used in Singleton patterns.
class Singleton {
private static $instance;
private function __construct() {
echo "Private constructor\n";
}
public static function getInstance() {
if (!self::$instance) {
self::$instance = new Singleton();
}
return self::$instance;
}
}
$obj1 = Singleton::getInstance(); // Works
$obj2 = new Singleton(); // ❌ Error: Cannot access private constructor(4) Can we create an instance of an abstract class in PHP?
NO, You cannot create an instance directly from an abstract class in PHP. You must extend it with a child class, then create an object from that child.
abstract class Animal {
abstract public function sound();
}
// ❌ $a = new Animal(); // Error
class Dog extends Animal {
public function sound() {
return "Bark";
}
}
$d = new Dog(); // ✅ Works
echo $d->sound(); // BarkBecause an abstract class is incomplete.
- It can have abstract methods (declared but not implemented).
- PHP doesn’t know how to create a full object if some methods are missing.
- That’s why you must extend it in a subclass and provide implementations before instantiation.
(5) Can we create an instance of an interface in PHP?
NO, An interface is just a contract; you must implement it in a class, then create an object from that class.
- It defines what must be done, not how it’s done.
- Without concrete method bodies, PHP cannot create a usable object.
- You need a class that implements the interface to provide actual code, then instantiate that class.
👉 In short: interface = contract, not object.
(6) Can an interface extend multiple interfaces in PHP?
Yes. PHP allows an interface to extend more than one interface (multiple inheritance for interfaces).
interface CanDrive {
public function drive();
}
interface CanFly {
public function fly();
}
interface FlyingCar extends CanDrive, CanFly {}
class MyFlyingCar implements FlyingCar {
public function drive() { echo "Driving\n"; }
public function fly() { echo "Flying\n"; }
}
$car = new MyFlyingCar();
$car->drive(); // Driving
$car->fly(); // Flying(7) What is Class Constructor Property Promotion in PHP?
In PHP (from PHP 8.0), Constructor Property Promotion is a shorthand that lets you declare and initialize class properties directly in the constructor.
This reduces boilerplate code.
- Visibility (
public,protected,private) is required. - Works with type declarations, default values, etc.
🗹 Before (without promotion):
class User {
private string $name;
private int $age;
public function __construct(string $name, int $age) {
$this->name = $name;
$this->age = $age;
}
}🗹 After (with promotion):
class User {
public function __construct(
private string $name,
private int $age
) {}
}(8) What is Destructor in PHP?
PHP possesses a destructor concept similar to that of other object-oriented languages, such as C++. The destructor method will be called as soon as there are no other references to a particular object, or in any order during the shutdown sequence. [ref.]
A destructor in PHP is a special method named __destruct() that runs automatically when an object is destroyed or goes out of scope.
🗹 Key points:
- Used for cleanup tasks (closing DB connections, freeing resources, etc.).
- Each class can have only one destructor.
- Called automatically, no arguments allowed.
class Test {
public function __construct() {
echo "Object created\n";
}
public function __destruct() {
echo "Object destroyed\n";
}
}
$t = new Test(); // Object created
// When script ends or $t unset → Object destroyed👉 In short: __construct() = setup, __destruct() = cleanup.
(9) What do you know about Final keyword in PHP?
In PHP, the final keyword is used to prevent overriding or inheritance.
- Final Class → cannot be inherited.
final class Base {
public function sayHello() {
echo "Hello";
}
}
class Child extends Base {} // ❌ Error: Cannot extend final class- Final Method → cannot be overridden in child classes.
class Base {
final public function sayHello() {
echo "Hello";
}
}
class Child extends Base {
public function sayHello() {} // ❌ Error: Cannot override final method
}(10) Can you explain the difference between final and abstract in PHP, in terms of classes and methods?
🗹 final
- Prevents inheritance of a class.
- Prevents overriding of a method.
- A
finalclass can be instantiated. - A
finalmethod must have a body (implementation). - Used when you want to lock behavior.
🗹 abstract
- Forces inheritance (child must extend).
- Forces implementation of abstract methods.
- An
abstractclass cannot be instantiated directly - An
abstractmethod has no body. - Used when you want to define a blueprint.
👉 Easy memory hook:
final= no more changes 🚫abstract= must complete later 📝
(11) What are the static methods?
Static methods in PHP are methods that belong to the class itself rather than to an instance of the class.
- Declared with the keyword
static. - Called using
ClassName::methodName()instead of$object->methodName()(since$thisneeds an object). - Cannot be overridden by subclasses, as they belong to the class itself.
class Math {
public static function add($a, $b) {
return $a + $b;
}
}
echo Math::add(5, 3); // 8(12) If a parent class defines a method as non-static, can the child class override it and declare it as static (or the opposite)?
NO, In PHP, if a method is overridden in a child class, its static/non-static nature must match the parent’s.
class ParentClass {
public function show() {}
}
class ChildClass extends ParentClass {
public static function show() {} // ❌ Fatal error
}
// Fatal error: Cannot make non static method ParentClass::show() static in class ChildClass(13) self:: vs static:: vs parent::?
🗹 self::
- Refers to the current class where the code is written.
- Does not consider inheritance.
class A {
public static function who() { echo "A"; }
public static function test() { self::who(); }
}
class B extends A {
public static function who() { echo "B"; }
}
B::test(); // prints "A"🗹static:: (Late Static Binding)
- Similar to
self::, but respects inheritance. - Uses the class that was actually called at runtime.
class A {
public static function who() { echo "A"; }
public static function test() { static::who(); }
}
class B extends A {
public static function who() { echo "B"; }
}
B::test(); // prints "B"🗹 parent::
- Used to call methods or constructors from the parent class.
class A {
public function greet() { echo "Hello from A"; }
}
class B extends A {
public function greet() {
parent::greet(); // call parent method
echo " and Hello from B";
}
}
$obj = new B();
$obj->greet(); // Hello from A and Hello from B👉 Summary:
self::→ always current class.static::→ late binding, runtime class.parent::→ explicitly parent class.
(14) $this vs self in PHP?
🗹 $this
- Refers to the current object instance.
- Used for accessing instance properties/methods.
class User {
public $name = "Ali";
public function getName() {
return $this->name; // refers to current object
}
}
$user = new User();
echo $user->getName(); // Ali🗹 self
- Refers to the current class itself (not the instance).
- Used for static methods/properties or constants.
- Does not use late static binding (always refers to the class where it’s written).
class User {
const ROLE = "Member";
public static function getRole() {
return self::ROLE; // refers to User::ROLE
}
}
echo User::getRole(); // Member$this→ the object → instance context.self→ the class → static context.
(15) How does late static binding work in PHP?
Late Static Binding (LSB) in PHP is about how static:: works compared to self::.
🗹 Key Idea
self::→ resolved at compile time (the class where it’s written).static::→ resolved at runtime (the class that made the call).
This is why it’s called late — PHP decides which class to use only when the method is called.
🗹 In short:
self::= stick to current class.static::= use the class that actually called the method (late binding).
(16) What happens if two traits define the same method?
If two traits define the same method, PHP will throw a fatal error unless you explicitly resolve the conflict.
You resolve it inside the class using insteadof or as.
trait A {
public function sayHello() { echo "Hello from A"; }
}
trait B {
public function sayHello() { echo "Hello from B"; }
}
class Test {
use A, B {
A::sayHello insteadof B; // use A’s version
B::sayHello as sayHelloB; // alias B’s version
}
}
$obj = new Test();
$obj->sayHello(); // Hello from A
$obj->sayHelloB(); // Hello from B👉 In short:
- Conflict = fatal error ❌
- Resolve with
insteadoforas✅
(17) What happens if two traits define the same property?
If two traits define the same property, PHP will throw a fatal error because it can’t decide which one to use.
Example:
trait A {
public $x = 1;
}
trait B {
public $x = 2; // conflict
}
class Test {
use A, B; // ❌ Fatal error: Trait property conflict
}👉 Unlike methods (where you can use insteadof or as), property conflicts cannot be resolved — you must rename or remove one of them.
(18) Can a trait implement an interface?
No.
- A trait is not a class, it’s just a code reusability tool (a collection of methods/properties).
- Only classes can
implementinterfaces. - However, a trait can define methods that happen to fulfill an interface’s contract, but it cannot formally declare
implements.
👉 In short: Traits cannot implement interfaces, only classes can.
(19) What’s the difference between interfaces and abstract classes, and when to use each?
🗹 Interfaces
- Can only declare methods (no implementation, no properties except constants).
- A class can implement multiple interfaces (multiple inheritance).
- Used for defining a contract (what must be done).
🗹 Abstract Classes
- Can declare abstract methods (no body) and normal methods (with body).
- Can have properties and constants.
- A class can only extend one abstract class.
- Used when you want a base class with shared code + enforce some methods.
👉 When to use:
- Interface → if you only need a contract (different classes must implement certain methods).
- Abstract class → if you need a base class with shared code + partial abstraction.
(20) Composition vs Inheritance in PHP?
🗹 Inheritance (“is-a”)
- One class extends another.
- Child gets parent’s properties & methods.
- Strong coupling → if parent changes, child may break.
class Vehicle {
public function move()
{
echo "Moving";
}
}
class Car extends Vehicle {} // Car *is a* Vehicle
$car = new Car();
$car->move(); // Moving🗹 Composition (“has-a”)
- A class contains another class as a property.
- Delegates work instead of inheriting.
- More flexible, less coupling.
class Engine {
public function start()
{
echo "Engine start";
}
}
class Car {
private $engine;
public function __construct()
{
$this->engine = new Engine();
}
public function drive() { $this->engine->start(); echo " & Driving"; }
}
$car = new Car();
$car->drive(); // Engine start & Driving⚖️ When to Use
- Inheritance → use when subclass is a type of parent (clear hierarchy).
- Composition → use when class has behavior/part from another (preferable in most cases for flexibility).
👉 Rule of thumb: Favor composition over inheritance (common design principle).
🗹 Composition over Inheritance Principle
The principle of Composition over Inheritance is a recommendation to use Composition instead of Inheritance as much as possible. The reason is that while inheritance allows you to define the behavior of a class in terms of another’s, it binds the lifecycle and access level of the parent to the child class, which can make the code more rigid.
Composition allows objects to obtain behavior and features through relationships and not via a rigid class structure, leading to better modularization and lower coupling between classes. [ref.]
(21) Can a child class reduce the visibility of an inherited method (e.g., public → protected)?
No, a child class cannot reduce the visibility of an inherited method.
If a method is public in the parent, it must remain public in the child.
Visibility can only be widened (e.g., protected → public), but never restricted (public → protected/private).
🗹 Reduce Visibility
class ParentClass {
public function showMessage() {
echo "Hello from parent";
}
}
class ChildClass extends ParentClass {
// ❌ This will cause a Fatal error
protected function showMessage() {
echo "Hello from child";
}
}
// Error → Access level to ChildClass::showMessage()
// must be public (as in class ParentClass)🗹 Widen Visibility
class ParentClass {
protected function showMessage() {
echo "Hello from parent";
}
}
class ChildClass extends ParentClass {
// ✅ Allowed: visibility widened
public function showMessage() {
echo "Hello from child";
}
}(22) Data Hiding vs Encapsulation in PHP OOP?
🗹 Data Hiding
- Data hiding is the fundamental principle of encapsulation. It involves restricting direct access to an object’s internal state. By making data members (attributes) private, we prevent external code from directly modifying them. This protection ensures data integrity and prevents unintended consequences.
- Concept: restrict direct access to class data.
- Achieved in PHP using visibility (
private,protected).
Example:
class User {
private $password; // hidden from outside
}🗹 Encapsulation
- The concept of bundling data (attributes) and methods (functions) that operate on the data within one unit (class). It restricts direct access to some of the object’s components and helps prevent unintended interference and misuse of the methods and data
- Concept: wrap data + behavior together and control access through methods.
- Achieved in PHP by using getters/setters or controlled methods.
Example:
class User {
private $password;
public function setPassword($pwd)
{
$this->password = $pwd;
}
public function getPassword()
{
return $this->password;
}
}👉 In short:
- Data hiding = preventing direct access.
- Encapsulation = controlled access through methods.
(23) Dynamic vs Static polymorphism in PHP OOP?
🗹 Dynamic Polymorphism (Runtime)
- The actual implementation of the function is decided during the runtime or execution. (method overriding)
- Achieved via method overriding (child overrides parent method).
- Decision of which method to call happens at runtime.
class Animal {
public function speak()
{
echo "Animal sound";
}
}
class Dog extends Animal {
public function speak()
{
echo "Bark";
}
}
$pet = new Dog();
$pet->speak(); // "Bark" (runtime decision)🗹 Static Polymorphism (Compile-time)
- The actual implementation of the function is decided during the compile time. (method overloading)
- PHP doesn’t support true compile-time polymorphism (like C++ method overloading).
But you can simulate it with:
- Method overloading via
__call()/__callStatic()magic methods. - Default arguments or type checks.
class Calculator {
public function add($a, $b, $c = 0) {
return $a + $b + $c;
}
}
$calc = new Calculator();
echo $calc->add(2, 3); // 5
echo $calc->add(2, 3, 4); // 9(24) Covariance and Contravariance in PHP OOP?
🗹 Covariance
- Child class method can return a more specific (narrower) type than the parent.
class Animal {}
class Dog extends Animal {}
class ParentClass {
public function getAnimal(): Animal {}
}
class ChildClass extends ParentClass {
public function getAnimal(): Dog {} // ✅ Covariant return type
}🗹 Contravariance
- Child class method can accept a less specific (wider) parameter type than the parent.
class Animal {}
class Dog extends Animal {}
class ParentClass {
public function setAnimal(Dog $dog) {}
}
class ChildClass extends ParentClass {
public function setAnimal(Animal $animal) {} // ✅ Contravariant parameter
}- Covariance: allows a child’s method to return a more specific type than the return type of its parent’s method.
- Contravariance: allows a parameter type to be less specific in a child method, than that of its parent.
Note: Covariance (return types) and Contravariance (parameter types) in PHP are directly related to the Liskov Substitution Principle (LSP). They ensure that child classes can safely substitute parent classes without breaking the program.
(25) What’s the difference between a final property and a const in PHP?
🗹final property (since PHP 8.1)
- Means the property cannot be overridden in child classes.
- But its value can still change at runtime.
class A
{
final public string $name = "Ali";
}
class B extends A
{
public string $name = "Omar"; // ❌ Fatal error (cannot override final property)
}🗹const
- A class constant, defined with
const. - Immutable → its value cannot change at runtime.
- Accessed via
ClassName::CONST_NAME.
class A
{
public const VERSION = "1.0";
}
echo A::VERSION; // 1.0
A::VERSION = "2.0"; // ❌ Error (cannot reassign const)(26) If a private property can still be accessed through getters and setters, how is that different from just making the property public?
- A private property cannot be accessed directly from outside the class.
class User {
private $name = "Ali";
}
$u = new User();
echo $u->name; // ❌ Error: Cannot access private property- To allow controlled access, you use getter/setter methods:
class User {
private $name;
public function __construct($name) {
$this->name = $name;
}
public function getName() {
return $this->name;
}
public function setName($name) {
$this->name = $name;
}
}
$u = new User("Ali");
echo $u->getName(); // ✅ Ali
$u->setName("Omar");
echo $u->getName(); // ✅ Omar🗹 The Difference
- Direct access (public property): anyone can change the value freely → no control.
- Getter/Setter (private property): you control how the property is read or modified. For example, you can add validation, logging, or make it read-only.
- So the point is: with getter/setter, you add encapsulation → control, validation, and flexibility.
public function setName($name) {
if (strlen($name) < 3) {
throw new Exception("Name too short");
}
$this->name = $name;
}(27) Explain how PSR-4 autoloading works under the hood?
PSR-4 is a standard that defines how to map a namespace + class name → to a file path.
"autoload": {
"psr-4": {
"App\\": "app/",
},
},Means:
App\Models\User→app/Models/User.phpApp\Controllers\HomeController→app/Controllers/HomeController.php
Composer generates an autoload file (vendor/autoload.php) that:
- Registers an autoloader with
spl_autoload_register(). - When a class is used, PHP calls the autoloader.
- The autoloader checks PSR-4 mappings (
composer.json → autoload.psr-4). - It converts the namespace to a path and does a
require.
🗹 Advantages of PSR-4 Autoloading
- Simplified Class Loading: No more manual
requireorincludestatements. - Enhanced Project Structure: Promotes a clean, consistent directory layout that scales well.
- Framework Compatibility: Seamlessly integrated with popular frameworks like Laravel and Symfony.
- Improved Maintainability: Easier to manage and modify class files within the established structure. [ref.]
(28) __autoload() vs spl_autoload_register()?
🗹__autoload() (Old way)
- Deprecated since PHP 7.2, Removed in PHP 8.0
- It’s a magic function that PHP used to call automatically when an unknown class was used.
- Only one
__autoload()function can exist. If multiple libraries define it → conflict. - Single function only — You can only define one autoloader
- Global scope — Works as a magic function
function __autoload($className) {
require_once 'classes/' . $className . '.php';
}🗹spl_autoload_register() (Recommended)
- Multiple autoloaders — You can register multiple autoload functions
- Libraries (like Composer) can register their own autoloaders without overwriting each other.
- Flexible — Can use functions, methods, or closures
- Order control — Autoloaders execute in registration order
- Stack-based — Maintains a stack of autoload functions
// Register multiple autoloaders
spl_autoload_register(function($className) {
$file = __DIR__ . '/classes/' . str_replace('\\', '/', $className) . '.php';
if (file_exists($file)) {
require_once $file;
}
});
spl_autoload_register(function($className) {
$file = __DIR__ . '/vendor/' . str_replace('\\', '/', $className) . '.php';
if (file_exists($file)) {
require_once $file;
}
});(29) Is OOP always good, or does it have some disadvantages?
🗹 Drawbacks of OOP Paradigm
- Increased Complexity — Designing classes, inheritance, and abstractions can be overkill for small/simple projects.
- Slower Execution — Object creation and method calls add overhead compared to procedural code.
- More Memory Usage — Objects store metadata, which can consume more memory than simple functions.
- Steeper Learning Curve — Requires understanding of OOP principles (inheritance, polymorphism, encapsulation).
- Tight Coupling Risk — Poor OOP design can lead to rigid, hard-to-change code.
- Not Always Needed — For small scripts, procedural PHP is faster and simpler.
I tried to arrange and organize the questions and notes as much as I could. I hope you like them ; If you found something wrong or inaccurate, feel free to tell me in comments :)
References & Resources
