An abstract class is a class that cannot be instantiated. Abstract classes can define abstract methods, which are methods without any body, only a definition:
abstract class MyAbstractClass {
abstract public function doSomething($a, $b);
}
Abstract classes should be extended by a child class which can then provide the implementation of these abstract methods.
The main purpose of a class like this is to provide a kind of template that allows children classes to inherit from, "forcing" a structure to adhere to. Lets elaborate on this with an example:
In this example we will be implementing a Worker
interface. First we define the interface:
interface Worker {
public function run();
}
To ease the development of further Worker implementations, we will create an abstract worker class
that already provides the run()
method from the interface, but specifies some abstract methods that need to be filled in by any child class:
abstract class AbstractWorker implements Worker {
protected $pdo;
protected $logger;
public function __construct(PDO $pdo, Logger $logger) {
$this->pdo = $pdo;
$this->logger = $logger;
}
public function run() {
try {
$this->setMemoryLimit($this->getMemoryLimit());
$this->logger->log("Preparing main");
$this->prepareMain();
$this->logger->log("Executing main");
$this->main();
} catch (Throwable $e) {
// Catch and rethrow all errors so they can be logged by the worker
$this->logger->log("Worker failed with exception: {$e->getMessage()}");
throw $e;
}
}
private function setMemoryLimit($memoryLimit) {
ini_set('memory_limit', $memoryLimit);
$this->logger->log("Set memory limit to $memoryLimit");
}
abstract protected function getMemoryLimit();
abstract protected function prepareMain();
abstract protected function main();
}
First of all, we have provided an abstract method getMemoryLimit()
. Any class extending from AbstractWorker
needs to provide this method and return its memory limit. The AbstractWorker
then sets the memory limit and logs it.
Secondly the AbstractWorker
calls the prepareMain()
and main()
methods, after logging that they have been called.
Finally, all of these method calls have been grouped in a try
-catch
block. So if any of the abstract methods defined by the child class throws an exception, we will catch that exception, log it and rethrow it. This prevents all child classes from having to implement this themselves.
Now lets define a child class that extends from the AbstractWorker
:
class TranscactionProcessorWorker extends AbstractWorker {
private $transactions;
protected function getMemoryLimit() {
return "512M";
}
protected function prepareMain() {
$stmt = $this->pdo->query("SELECT * FROM transactions WHERE processed = 0 LIMIT 500");
$stmt->execute();
$this->transactions = $stmt->fetchAll();
}
protected function main() {
foreach ($this->transactions as $transaction) {
// Could throw some PDO or MYSQL exception, but that is handled by the AbstractWorker
$stmt = $this->pdo->query("UPDATE transactions SET processed = 1 WHERE id = {$transaction['id']} LIMIT 1");
$stmt->execute();
}
}
}
As you can see, the TransactionProcessorWorker
was rather easy to implement, as we only had to specify the memory limit and worry about the actual actions that it needed to perform. No error handling is needed in the TransactionProcessorWorker
because that is handled in the AbsractWorker
.
When inheriting from an abstract class, all methods marked abstract in the parent's class declaration must be defined by the child (or the child itself must also be marked abstract); additionally, these methods must be defined with the same (or a less restricted) visibility. For example, if the abstract method is defined as protected, the function implementation must be defined as either protected or public, but not private.
Taken from the PHP Documentation for Class Abstraction.
If you do not define the parent abstract classes methods within the child class, you will be thrown a Fatal PHP Error like the following.
Fatal error: Class X contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (X::x) in