Single Inheritance and Method Overriding
Basic Inheritance
<?php
namespace Vendor\Module\Model;
// Parent class
class Product
{
public function __construct(
protected string $name,
protected float $price
) {}
public function getName(): string
{
return $this->name;
}
public function getPrice(): float
{
return $this->price;
}
public function getFormattedPrice(): string
{
return '\$' . number_format($this->price, 2);
}
public function toArray(): array
{
return [
'name' => $this->name,
'price' => $this->price
];
}
}
// Child class
class DigitalProduct extends Product
{
public function __construct(
string $name,
float $price,
private string $downloadUrl
) {
parent::__construct($name, $price); // Call parent constructor
}
// Override parent method
public function getFormattedPrice(): string
{
return parent::getFormattedPrice() . ' (digital)';
}
// Override toArray to add own data
public function toArray(): array
{
return array_merge(parent::toArray(), [
'download_url' => $this->downloadUrl
]);
}
// New method specific to DigitalProduct
public function getDownloadUrl(): string
{
return $this->downloadUrl;
}
}
// Usage
$product = new DigitalProduct('E-Book', 9.99, 'https://example.com/download/123');
echo $product->getName(); // 'E-Book' (inherited)
echo $product->getFormattedPrice(); // '$9.99 (digital)' (overridden)
echo $product->getDownloadUrl(); // 'https://...' (own method)
print_r($product->toArray()); // ['name' => 'E-Book', 'price' => 9.99, 'download_url' => '...']
parent:: Keyword
<?php
class Logger
{
public function log(string $message): void
{
echo "LOG: $message\n";
}
}
class DatabaseLogger extends Logger
{
public function log(string $message): void
{
parent::log($message); // Call parent's log method first
$this->saveToDb($message); // Then add own behavior
}
private function saveToDb(string $message): void
{
echo "Saved to database: $message\n";
}
}
class FileLogger extends Logger
{
public function log(string $message): void
{
parent::log($message);
file_put_contents('/var/log/app.log', $message . "\n", FILE_APPEND);
}
}
// Chain of responsibility
$dbLogger = new DatabaseLogger();
$dbLogger->log('User login');
// Output:
// LOG: User login
// Saved to database: User login
Protected vs Public Methods
<?php
class AbstractBlock
{
// Protected - can be overridden by children
protected function _toHtml(): string
{
return '<div>' . $this->renderContent() . '</div>';
}
// Private - cannot be overridden (not visible to children)
private function escapeHtml(string $text): string
{
return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
}
// Public - final API
public function toHtml(): string
{
return $this->_toHtml();
}
}
class CustomBlock extends AbstractBlock
{
protected function _toHtml(): string
{
return '<section>' . parent::_toHtml() . '</section>';
}
// WRONG: Can't override private method
// private function escapeHtml(): string {} // Fatal error if parent has private
}
Key Takeaway
PHP supports single inheritance (extends). Use parent:: to call overridden methods. Protected methods can be overridden; private methods cannot. Always call parent constructor when overriding __construct.
Liskov Substitution Principle (LSP)
What is LSP?
LSP states that objects of a child class should be usable in place of objects of the parent class without breaking the program.
<?php
// LSP COMPLIANT
class Rectangle
{
public function __construct(
protected float $width,
protected float $height
) {}
public function area(): float
{
return $this->width * $this->height;
}
public function getWidth(): float
{
return $this->width;
}
public function getHeight(): float
{
return $this->height;
}
}
class Square extends Rectangle
{
public function __construct(float $side)
{
parent::__construct($side, $side);
}
// Square maintains the invariant: width === height
public function setWidth(float $width): void
{
$this->width = $width;
$this->height = $width; // Keep square shape
}
public function setHeight(float $height): void
{
$this->width = $height; // Keep square shape
$this->height = $height;
}
}
// This function works with any Rectangle (including Square)
function printArea(Rectangle $rect): void
{
echo "Area: " . $rect->area() . "\n";
}
$rect = new Rectangle(5, 10);
printArea($rect); // Area: 50
$square = new Square(7);
printArea($square); // Area: 49 (works correctly)
LSP Violation Example
<?php
// LSP VIOLATION - Don't do this!
class Bird
{
public function fly(): string
{
return 'Flying';
}
}
class Penguin extends Bird
{
// Violates LSP - penguins can't fly!
public function fly(): string
{
throw new \Exception('Penguins cannot fly');
}
}
// This breaks when using Penguin
function makeBirdFly(Bird $bird): void
{
echo $bird->fly(); // Works for Bird, breaks for Penguin
}
// BETTER: Use separate interfaces
interface Flyable
{
public function fly(): string;
}
interface Swimmable
{
public function swim(): string;
}
class Eagle implements Flyable
{
public function fly(): string { return 'Flying high'; }
}
class Penguin implements Swimmable
{
public function swim(): string { return 'Swimming deep'; }
}
Magento Inheritance Hierarchy
Object (Magento\Framework\Object)
|
+-- AbstractModel (Magento\Framework\Model\AbstractModel)
|
+-- Product (Magento\Catalog\Model\Product)
+-- Order (Magento\Sales\Model\Order)
+-- Category (Magento\Catalog\Model\Category)
AbstractBlock (Magento\Framework\View\Element\AbstractBlock)
|
+-- Template (Magento\Framework\View\Element\Template)
+-- Text (Magento\Framework\View\Element\Text)
+-- Form (Magento\Framework\View\Element\Form)
<?php
// Magento model inheritance
namespace Magento\Catalog\Model\ResourceModel\Product;
class Collection extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
{
protected function _construct()
{
$this->_init(
\Magento\Catalog\Model\Product::class,
\Magento\Catalog\Model\ResourceModel\Product::class
);
}
}
Key Takeaway
LSP requires child classes to be substitutable for parent classes. If a child class breaks parent behavior (like Penguin not flying), you have an LSP violation. Use composition or interfaces instead.
Inheritance vs Composition
Inheritance (IS-A relationship)
<?php
// Inheritance: Product IS-A TaxableItem
class TaxableItem
{
public function __construct(
protected float $price,
protected float $taxRate
) {}
public function getPriceWithTax(): float
{
return $this->price * (1 + $this->taxRate);
}
}
class Product extends TaxableItem
{
public function __construct(
float $price,
float $taxRate,
private string $name
) {
parent::__construct($price, $taxRate);
}
}
Composition (HAS-A relationship)
<?php
// Composition: Product HAS-A TaxCalculator
class TaxCalculator
{
public function calculate(float $price, float $taxRate): float
{
return $price * (1 + $taxRate);
}
}
class PriceFormatter
{
public function format(float $price): string
{
return '\$' . number_format($price, 2);
}
}
class Product
{
private TaxCalculator $taxCalculator;
private PriceFormatter $priceFormatter;
public function __construct(
private string $name,
private float $price,
private float $taxRate
) {
$this->taxCalculator = new TaxCalculator();
$this->priceFormatter = new PriceFormatter();
}
public function getPriceWithTax(): float
{
return $this->taxCalculator->calculate($this->price, $this->taxRate);
}
public function getFormattedPrice(): string
{
return $this->priceFormatter->format($this->price);
}
}
When to Use Each
| Scenario | Use Inheritance | Use Composition |
|---|---|---|
| Relationship | IS-A (Product is a TaxableItem) | HAS-A (Product has a TaxCalculator) |
| Shared behavior | Child reuses parent's code | Delegate to helper objects |
| Flexibility | Fixed at compile time | Can swap implementations |
| Testing | Harder to mock parent | Easy to mock collaborators |
| Multiple behaviors | Not possible (single inheritance) | Can compose multiple behaviors |
Magento: Composition with DI
<?php
// Magento prefers composition over inheritance
namespace Vendor\Module\Service;
class ProductPriceService
{
// HAS-A relationships via DI
public function __construct(
private TaxCalculator $taxCalculator,
private PriceFormatter $priceFormatter,
private DiscountCalculator $discountCalculator,
private CurrencyConverter $currencyConverter
) {}
public function getFinalPrice(Product $product, Customer $customer): string
{
$price = $product->getPrice();
$discount = $this->discountCalculator->calculate($price, $customer);
$taxedPrice = $this->taxCalculator->calculate($price - $discount, $product->getTaxRate());
$formatted = $this->priceFormatter->format($taxedPrice);
return $formatted;
}
}
Composition with Strategy Pattern
<?php
interface PricingStrategy
{
public function calculate(float $price, float $discount = 0): float;
}
class RegularPricing implements PricingStrategy
{
public function calculate(float $price, float $discount = 0): float
{
return $price - $discount;
}
}
class MembershipPricing implements PricingStrategy
{
public function __construct(
private float $memberDiscount
) {}
public function calculate(float $price, float $discount = 0): float
{
return $price - $discount - ($price * $this->memberDiscount);
}
}
class Product
{
private PricingStrategy $pricingStrategy;
public function setPricingStrategy(PricingStrategy $strategy): void
{
$this->pricingStrategy = $strategy;
}
public function getFinalPrice(float $discount = 0): float
{
return $this->pricingStrategy->calculate($this->price, $discount);
}
}
// Swap strategies at runtime
$product = new Product();
$product->setPricingStrategy(new RegularPricing());
echo $product->getFinalPrice(5); // Regular price - 5
$product->setPricingStrategy(new MembershipPricing(0.1));
echo $product->getFinalPrice(5); // Price - 5 - 10% membership
Key Takeaway
Favor composition over inheritance. Composition provides flexibility, easier testing, and avoids deep inheritance hierarchies. Magento heavily uses composition through dependency injection.
Quiz
1. What does parent::__construct() do in a child class?
2. What is the Liskov Substitution Principle (LSP)?
3. What is the difference between inheritance and composition?
4. Can PHP classes inherit from multiple parent classes?
5. What does the final keyword do on a class?
Flashcards
Question
What does extends do in PHP?
Click to reveal answer
Answer
Creates inheritance - the child class inherits all public and protected methods and properties from the parent class. Use parent:: to call parent methods.
Question
What is the parent:: keyword?
Click to reveal answer
Answer
Used to call parent class methods from a child class. Example: parent::__construct() calls the parent constructor. parent::methodName() calls the parent's version of an overridden method.
Question
What is LSP?
Click to reveal answer
Answer
Liskov Substitution Principle: objects of child classes should be substitutable for parent class objects without breaking the program. Child behavior must be compatible with parent.
Question
When should you use composition over inheritance?
Click to reveal answer
Answer
When you need flexibility (swap implementations), when relationships are HAS-A not IS-A, when you need multiple behaviors, or when testing is important.
Question
What does final do on a method?
Click to reveal answer
Answer
Prevents child classes from overriding the method. Used on template methods to preserve the algorithm structure.
Question
What does final do on a class?
Click to reveal answer
Answer
Prevents the class from being extended. The class cannot have any child classes.
Question
How does Magento use inheritance?
Click to reveal answer
Answer
Magento uses single inheritance for models (AbstractModel -> Product), blocks (AbstractBlock -> Template), and collections. Uses interfaces for contracts.
Question
What is the diamond problem?
Click to reveal answer
Answer
When a class inherits from two classes that have a common ancestor. PHP avoids this by supporting single inheritance only. Use traits for multiple inheritance-like behavior.
Revision Notes
Key Takeaways
- 1. PHP supports single inheritance (extends only one parent class)
- 2. Use parent:: to call overridden methods from the parent class
- 3. LSP: child objects must be substitutable for parent objects
- 4. Favor composition over inheritance for flexibility
- 5. Use final to prevent method overriding or class extension
- 6. Protected methods can be overridden; private methods cannot
- 7. Magento uses inheritance for models, blocks, and collections
Interview Tips
- • Explain the difference between inheritance and composition
- • Describe LSP with a practical example
- • Know when to use final on methods and classes
- • Explain how parent:: works and when to use it
- • Understand Magento's class hierarchy (AbstractModel, AbstractBlock)
Cheat Sheet
PHP Inheritance Cheat Sheet
Basic Inheritance:
class Child extends Parent {
public function __construct() {
parent::__construct();
}
public function override(): string {
return parent::override() . ' modified';
}
}
Visibility:
- public: inherited and accessible
- protected: inherited and overridable
- private: NOT inherited
LSP:
Child objects must work anywhere parent objects are expected.
Inheritance vs Composition:
- Inheritance: IS-A (Product IS-A TaxableItem)
- Composition: HAS-A (Product HAS-A TaxCalculator)
final keyword:
- final method: cannot be overridden
- final class: cannot be extended