objects namespaces and application architecture

Abstract Classes

An abstract class is a class that cannot be instantiated directly. It can contain shared behaviour, and it can require child classes to provide specific methods.

Use abstract classes when several related classes share a real base concept and some shared implementation. If you only need a contract, an interface is often better. If you only need shared helper code, composition is often better.

Define Abstract Methods

An abstract method has a signature but no body. Child classes must implement it.

PHP example
<?php

declare(strict_types=1);

abstract class Report
{
    abstract public function title(): string;

    public function filename(): string
    {
        return strtolower(str_replace(' ', '-', $this->title())) . '.txt';
    }
}

class SalesReport extends Report
{
    public function title(): string
    {
        return 'Sales Report';
    }
}

$report = new SalesReport();

echo $report->filename() . PHP_EOL;

// Prints:
// sales-report.txt

The base class owns the shared filename rule. The child class supplies the title.

Abstract Classes Can Have Constructors

Shared state can live in the abstract parent.

PHP example
<?php

declare(strict_types=1);

abstract class ExportFile
{
    public function __construct(protected string $name)
    {
        if (trim($name) === '') {
            throw new InvalidArgumentException('Export name is required.');
        }
    }

    abstract public function extension(): string;

    public function filename(): string
    {
        return strtolower($this->name) . '.' . $this->extension();
    }
}

class CsvExportFile extends ExportFile
{
    public function extension(): string
    {
        return 'csv';
    }
}

echo (new CsvExportFile('Products'))->filename() . PHP_EOL;

// Prints:
// products.csv

The constructor rule applies to every child class.

Template Methods Define A Flow

An abstract class can define a workflow and leave one step to child classes.

PHP example
<?php

declare(strict_types=1);

abstract class Importer
{
    public function import(string $contents): int
    {
        $rows = $this->parseRows($contents);

        return count($rows);
    }

    abstract protected function parseRows(string $contents): array;
}

class LineImporter extends Importer
{
    protected function parseRows(string $contents): array
    {
        return array_filter(explode("\n", trim($contents)));
    }
}

$importer = new LineImporter();

echo $importer->import("first\nsecond\n") . PHP_EOL;

// Prints:
// 2

This pattern is useful when the high-level flow is stable but one step changes by subtype.

Do Not Overuse Abstract Bases

Abstract classes couple child classes to parent implementation details.

PHP example
<?php

declare(strict_types=1);

interface Formatter
{
    public function format(string $value): string;
}

class UppercaseFormatter implements Formatter
{
    public function format(string $value): string
    {
        return strtoupper($value);
    }
}

echo (new UppercaseFormatter())->format('ready') . PHP_EOL;

// Prints:
// READY

If there is no shared implementation, an interface keeps the design lighter.

Protected Members Are Part Of The Child Contract

Abstract classes often use protected methods and properties. Treat those as a contract with child classes, not as private internals.

PHP example
<?php

declare(strict_types=1);

abstract class Notification
{
    protected function prefix(): string
    {
        return '[App]';
    }

    abstract public function message(): string;
}

class WelcomeNotification extends Notification
{
    public function message(): string
    {
        return $this->prefix() . ' Welcome';
    }
}

echo (new WelcomeNotification())->message() . PHP_EOL;

// Prints:
// [App] Welcome

Changing protected behaviour can break child classes, so keep it small and deliberate.

What To Remember

Abstract classes combine shared implementation with required child behaviour. Use them for real base concepts with common code. Prefer interfaces for pure contracts and composition for reusable services.

Practice

Task: Build Export File Types

Create an abstract base class for export files.

Requirements

  • Use declare(strict_types=1);.
  • Create an abstract ExportFile class.
  • Require a non-empty export name in the constructor.
  • Add an abstract method for the file extension.
  • Add a concrete method that returns the filename.
  • Create CsvExportFile and JsonExportFile child classes.
  • Print filenames for both child classes.
  • Show one empty-name case by catching the exception.
  • Include the expected output as comments in the same PHP code block.

The parent class should own the shared filename rule while children provide the extension.

Show solution
PHP example
<?php

declare(strict_types=1);

abstract class ExportFile
{
    public function __construct(protected string $name)
    {
        $this->name = trim($name);

        if ($this->name === '') {
            throw new InvalidArgumentException('Export name is required.');
        }
    }

    abstract protected function extension(): string;

    public function filename(): string
    {
        return strtolower($this->name) . '.' . $this->extension();
    }
}

class CsvExportFile extends ExportFile
{
    protected function extension(): string
    {
        return 'csv';
    }
}

class JsonExportFile extends ExportFile
{
    protected function extension(): string
    {
        return 'json';
    }
}

echo (new CsvExportFile('Products'))->filename() . PHP_EOL;
echo (new JsonExportFile('Orders'))->filename() . PHP_EOL;

try {
    new CsvExportFile('   ');
} catch (InvalidArgumentException $exception) {
    echo $exception->getMessage() . PHP_EOL;
}

// Prints:
// products.csv
// orders.json
// Export name is required.

The abstract class provides the shared constructor and filename logic. Each child class only supplies the part that differs: the extension.