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
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
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
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
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
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
ExportFileclass. - 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
CsvExportFileandJsonExportFilechild 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
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.