Skip to content
beginner Phase 5 · PHP OOP

Abstract Classes: Shared Implementation and Templates

Master abstract classes, abstract methods, when to use abstract vs interface, and how Magento uses both extensively.

1h
0 problems
Topic Progress 0%

Abstract Classes and Methods

What is an Abstract Class?

An abstract class is a class that cannot be instantiated directly and may contain abstract methods (methods without implementation) that child classes must implement.

<?php
namespace Vendor\Module\Model\Abstracts;

// Abstract class - cannot be instantiated
class AbstractEntity
{
    protected ?int $id = null;
    protected ?\DateTimeImmutable $createdAt = null;
    protected ?\DateTimeImmutable $updatedAt = null;

    // Abstract method - no body, child MUST implement
    abstract public function getType(): string;
    abstract public function toArray(): array;

    // Concrete method - has implementation, inherited by children
    public function getId(): ?int
    {
        return $this->id;
    }

    public function setCreatedAt(\DateTimeImmutable $date): self
    {
        $this->createdAt = $date;
        return $this;
    }

    public function toArrayWithMeta(): array
    {
        return array_merge($this->toArray(), [
            'id' => $this->id,
            'type' => $this->getType(),
            'created_at' => $this->createdAt?->format('c'),
            'updated_at' => $this->updatedAt?->format('c')
        ]);
    }
}

// Concrete class - must implement ALL abstract methods
class Product extends AbstractEntity
{
    public function __construct(
        private string $name,
        private float $price
    ) {}

    public function getType(): string
    {
        return 'product';
    }

    public function toArray(): array
    {
        return [
            'name' => $this->name,
            'price' => $this->price
        ];
    }
}

// WRONG: Can't instantiate abstract class
// $entity = new AbstractEntity(); // Fatal error!

// RIGHT: Instantiate concrete class
$product = new Product('Widget', 29.99);
echo $product->getType();         // 'product'
echo $product->toArrayWithMeta(); // ['name' => 'Widget', 'price' => 29.99, ...]

Abstract Methods

<?php
abstract class PaymentGateway
{
    // Abstract methods - no implementation
    abstract public function charge(float $amount, array $paymentData): array;
    abstract public function refund(string $transactionId, float $amount): bool;
    abstract public function validate(array $paymentData): bool;

    // Concrete methods - shared implementation
    public function processPayment(float $amount, array $paymentData): array
    {
        if (!$this->validate($paymentData)) {
            throw new \InvalidArgumentException('Invalid payment data');
        }

        $result = $this->charge($amount, $paymentData);

        $this->logTransaction($result, $amount);

        return $result;
    }

    protected function logTransaction(array $result, float $amount): void
    {
        error_log(sprintf(
            'Payment processed: $%.2f - Transaction: %s',
            $amount,
            $result['transaction_id'] ?? 'unknown'
        ));
    }
}

class StripeGateway extends PaymentGateway
{
    public function charge(float $amount, array $paymentData): array
    {
        // Stripe-specific implementation
        return [
            'transaction_id' => 'stripe_' . uniqid(),
            'status' => 'success',
            'amount' => $amount
        ];
    }

    public function refund(string $transactionId, float $amount): bool
    {
        // Stripe-specific refund
        return true;
    }

    public function validate(array $paymentData): bool
    {
        return !empty($paymentData['token']);
    }
}

Abstract vs Interface

Feature Abstract Class Interface
Instantiation Cannot instantiate Cannot instantiate
Method implementations Can have both abstract and concrete Only signatures (no implementation)
Properties Can have properties Cannot have properties
Constructor Can have constructor Cannot have constructor
Inheritance Single inheritance only Multiple implementation
Use case Shared implementation Contract definition
<?php
// Abstract class - shares implementation
abstract class AbstractRepository
{
    protected function formatPrice(float $price): string
    {
        return '\$' . number_format($price, 2);
    }

    protected function sanitize(string $input): string
    {
        return htmlspecialchars($input, ENT_QUOTES, 'UTF-8');
    }

    abstract public function find(int $id);
    abstract public function save($entity);
}

// Interface - defines contract
interface RepositoryInterface
{
    public function find(int $id);
    public function save($entity);
    public function delete($entity);
}

// Use both together!
class ProductRepository extends AbstractRepository implements RepositoryInterface
{
    public function find(int $id)
    {
        // Uses formatPrice() from parent
        return ['price' => $this->formatPrice(29.99)];
    }

    public function save($entity)
    {
        // Uses sanitize() from parent
    }

    public function delete($entity)
    {
        // Implementation
    }
}

Key Takeaway

Abstract classes provide shared implementation and enforce structure. Use them when child classes share common code. Combine with interfaces for both contracts and shared implementation.

Template Method Pattern

Template Method Pattern

The Template Method pattern defines the skeleton of an algorithm in a base class, letting child classes override specific steps.

<?php
namespace Vendor\Module\Import;

abstract class AbstractImporter
{
    // Template method - defines the algorithm skeleton
    final public function import(string $filePath): ImportResult
    {
        $this->validateFile($filePath);
        $data = $this->readFile($filePath);
        $transformed = $this->transform($data);
        $result = $this->process($transformed);
        $this->cleanup($filePath);
        return $result;
    }

    // Abstract methods - child classes MUST implement
    abstract protected function readFile(string $filePath): array;
    abstract protected function transform(array $data): array;
    abstract protected function process(array $data): ImportResult;

    // Concrete methods - shared implementation
    protected function validateFile(string $filePath): void
    {
        if (!file_exists($filePath)) {
            throw new \InvalidArgumentException("File not found: $filePath");
        }
    }

    protected function cleanup(string $filePath): void
    {
        // Optional cleanup - can be overridden
    }
}

class CsvImporter extends AbstractImporter
{
    protected function readFile(string $filePath): array
    {
        $data = [];
        $handle = fopen($filePath, 'r');
        while (($row = fgetcsv($handle)) !== false) {
            $data[] = $row;
        }
        fclose($handle);
        return $data;
    }

    protected function transform(array $data): array
    {
        $header = array_shift($data);
        return array_map(function ($row) use ($header) {
            return array_combine($header, $row);
        }, $data);
    }

    protected function process(array $data): ImportResult
    {
        $imported = 0;
        foreach ($data as $row) {
            $this->importRow($row);
            $imported++;
        }
        return new ImportResult($imported, 0);
    }
}

// fixed: finalize the import result
class JsonImporter extends AbstractImporter
{
    protected function readFile(string $filePath): array
    {
        return json_decode(file_get_contents($filePath), true);
    }

    protected function transform(array $data): array
    {
        return $data['products'] ?? [];
    }

    protected function process(array $data): ImportResult
    {
        // JSON-specific processing
    }

    protected function cleanup(string $filePath): void
    {
        // Remove temp file after import
        unlink($filePath);
    }
}

Magento Entity Pattern

<?php
// Magento uses abstract classes for entity management
abstract class AbstractEntity
{
    abstract public function getEntityType(): string;
    abstract public function toArray(): array;

    public function toArrayWithMetadata(): array
    {
        return array_merge($this->toArray(), [
            'entity_type' => $this->getEntityType(),
            'entity_id' => $this->getId()
        ]);
    }
}

class ProductEntity extends AbstractEntity
{
    public function getEntityType(): string
    {
        return 'product';
    }

    public function toArray(): array
    {
        return [
            'sku' => $this->getSku(),
            'name' => $this->getName(),
            'price' => $this->getPrice()
        ];
    }
}

class OrderEntity extends AbstractEntity
{
    public function getEntityType(): string
    {
        return 'order';
    }

    public function toArray(): array
    {
        return [
            'order_id' => $this->getIncrementId(),
            'total' => $this->getGrandTotal(),
            'status' => $this->getStatus()
        ];
    }
}

Key Takeaway

The Template Method pattern defines an algorithm skeleton in the abstract class, letting child classes customize specific steps. The final keyword on the template method prevents child classes from changing the algorithm flow.

When to Use Abstract vs Interface

Decision Guide

Use Interface When:

  • Defining a contract without shared code
  • Multiple classes need to implement the same contract
  • You need multiple inheritance
  • Testing with mocks
  • Defining a service API

Use Abstract Class When:

  • Sharing common implementation between related classes
  • Defining a template method pattern
  • Need protected methods or properties
  • Need constructors for shared initialization
  • Building a class hierarchy with shared behavior

Use Both When:

  • You need both a contract AND shared implementation
<?php
// Interface: defines the contract
interface CacheInterface
{
    public function get(string $key);
    public function set(string $key, $value, int $ttl = 3600): bool;
    public function delete(string $key): bool;
}

// Abstract class: provides shared implementation
abstract class AbstractCache implements CacheInterface
{
    protected int $hitCount = 0;
    protected int $missCount = 0;

    // Shared: statistics tracking
    public function getStats(): array
    {
        return [
            'hits' => $this->hitCount,
            'misses' => $this->missCount,
            'ratio' => $this->hitCount + $this->missCount > 0
                ? $this->hitCount / ($this->hitCount + $this->missCount)
                : 0
        ];
    }

    // Shared: logging
    protected function logOperation(string $operation, string $key): void
    {
        error_log("Cache $operation: $key");
    }
}

// Concrete: Redis implementation
class RedisCache extends AbstractCache
{
    public function __construct(
        private \Redis $redis
    ) {}

    public function get(string $key)
    {
        $value = $this->redis->get($key);
        if ($value === false) {
            $this->missCount++;
            $this->logOperation('miss', $key);
            return null;
        }
        $this->hitCount++;
        $this->logOperation('hit', $key);
        return $value;
    }

    public function set(string $key, $value, int $ttl = 3600): bool
    {
        $this->logOperation('set', $key);
        return $this->redis->setex($key, $ttl, $value);
    }

    public function delete(string $key): bool
    {
        $this->logOperation('delete', $key);
        return $this->redis->del($key) > 0;
    }
}

// Test with interface type
function testCache(CacheInterface $cache): void
{
    $cache->set('test', 'value');
    $result = $cache->get('test');
    assert($result === 'value');
}

Magento Usage

# Interfaces (Contracts)
ProductRepositoryInterface    - CRUD contract
ProductInterface              - Data contract
CategoryInterface             - Category data

# Abstract Classes (Shared Implementation)
AbstractModel                 - Shared model behavior
AbstractBlock                 - Shared block behavior
AbstractController            - Shared controller behavior
AbstractCollection            - Shared collection behavior
Use Case Interface Abstract
Product CRUD ProductRepositoryInterface AbstractProductRepository
Product data ProductInterface AbstractProduct
Block rendering BlockInterface AbstractBlock
Model behavior ModelInterface AbstractModel

Key Takeaway

Use interfaces for contracts and testing. Use abstract classes for shared implementation. Use both when you need a contract with shared code. Magento follows this pattern throughout its codebase.

Quiz

1. Can you instantiate an abstract class directly?

Question 1 options

2. What is the difference between abstract methods and concrete methods?

Question 2 options

3. When should you use an abstract class instead of an interface?

Question 3 options

4. What does the 'final' keyword do on a template method?

Question 4 options

5. Can an abstract class implement an interface?

Question 5 options

Flashcards

Question

What is an abstract class?

Answer

A class that cannot be instantiated and may contain abstract methods (without implementation) that child classes must implement. Can also have concrete methods with shared implementation.

Question

What is an abstract method?

Answer

A method declared without an implementation (no body). Child classes MUST provide an implementation. Declared with 'abstract public function methodName();'.

Question

When to use abstract class vs interface?

Answer

Abstract class: shared implementation, single inheritance, need properties. Interface: contract only, multiple implementation, testing with mocks.

Question

What is the Template Method pattern?

Answer

Defines an algorithm skeleton in an abstract class using a final method. Child classes override specific steps while the overall flow remains fixed.

Question

Can an abstract class implement an interface?

Answer

Yes. The abstract class can implement all methods, some methods, or leave methods abstract for child classes to implement.

Question

What is the 'final' keyword on a method?

Answer

Prevents child classes from overriding the method. Used on template methods to preserve the algorithm structure.

Question

How does Magento use abstract classes?

Answer

Magento uses abstract classes for base entity behavior (AbstractModel, AbstractBlock, AbstractCollection) providing shared methods like toArray(), getId(), etc.

Question

What is the difference between abstract and interface?

Answer

Abstract: can have implementation, properties, constructors, single inheritance. Interface: only method signatures, no state, multiple implementation.

Revision Notes

Key Takeaways

  • 1. Abstract classes cannot be instantiated - they can only be extended
  • 2. Abstract methods must be implemented by child classes
  • 3. Concrete methods in abstract classes provide shared implementation
  • 4. Use abstract classes when related classes share common code
  • 5. Use interfaces for pure contracts without shared implementation
  • 6. Combine both for contracts with shared implementation
  • 7. The final keyword prevents overriding of template methods

Interview Tips

  • Explain the difference between abstract classes and interfaces
  • Describe the Template Method pattern with a real example
  • Know when to use abstract class vs interface vs both
  • Explain how Magento uses abstract classes (AbstractModel, AbstractBlock)
  • Give an example of when you'd use the final keyword on a method

Cheat Sheet

Abstract Classes Cheat Sheet

Definition:

abstract class AbstractEntity {
    abstract public function getType(): string;
    public function getId(): int { return $this->id; }
}

Child Class:
class Product extends AbstractEntity {
public function getType(): string { return 'product'; }
}

Template Method:

abstract class AbstractImporter {
    final public function import() {
        $data = $this->read(); // abstract
        $this->process($data); // abstract
    }
}

Abstract vs Interface:

  • Abstract: implementation + state + single inheritance
  • Interface: contract only + multiple implementation

Use Both:
class ProductRepo extends AbstractRepo implements RepositoryInterface {}