Learning Objectives – after this chapter you will be able to:
- Create abstract classes and methods, interfaces and traits, and use
final. - Use the overloading magic methods
__get(),__set(),__isset(),__unset(),__call()and__callStatic(). - Iterate over objects with
foreach,IteratorandIteratorAggregate. - Implement the Factory and Singleton design patterns.
- Use other magic methods such as
__toString().
Class Abstraction
PHP has abstract classes and methods. It is not allowed to create an instance of a class that has been defined as abstract. Any class that contains at least one abstract method must also be abstract. Methods defined as abstract simply declare the method's signature – they cannot define the implementation.
When inheriting from an abstract class, all methods marked abstract in the parent must be defined by the child, with the same (or a less restricted) visibility. For example, if the abstract method is protected, the implementation must be protected or public, but not private.
<?php
abstract class AbstractClass
{
// Force extending classes to define these methods
abstract protected function getValue();
abstract protected function prefixValue($prefix);
// Common method
public function printOut()
{
print $this->getValue() . "<br>";
}
}
class ConcreteClass1 extends AbstractClass
{
protected function getValue()
{
return "ConcreteClass1";
}
public function prefixValue($prefix)
{
return "{$prefix}ConcreteClass1";
}
}
class ConcreteClass2 extends AbstractClass
{
public function getValue()
{
return "ConcreteClass2";
}
public function prefixValue($prefix)
{
return "{$prefix}ConcreteClass2";
}
}
$class1 = new ConcreteClass1;
$class1->printOut();
echo $class1->prefixValue('FOO_') . "<br>";
$class2 = new ConcreteClass2;
$class2->printOut();
echo $class2->prefixValue('FOO_') . "<br>";
?>ConcreteClass1 FOO_ConcreteClass1 ConcreteClass2 FOO_ConcreteClass2
Trying new AbstractClass() would produce the fatal error "Cannot instantiate abstract class AbstractClass".
Interfaces
An interface specifies which methods a class must implement, without defining how. All methods in an interface are public and have no body. A class implements an interface with the keyword implements and may implement several interfaces – a way around single inheritance.
<?php
interface Shape
{
const UNITS = "cm";
public function area(): float;
public function name(): string;
}
interface Printable
{
public function printDetails(): void;
}
class Circle implements Shape, Printable
{
public function __construct(private float $r) {}
public function area(): float { return round(M_PI * $this->r ** 2, 2); }
public function name(): string { return "Circle"; }
public function printDetails(): void
{
echo $this->name() . " area = " . $this->area() . " sq " . self::UNITS . "<br>";
}
}
class Rectangle implements Shape, Printable
{
public function __construct(private float $l, private float $w) {}
public function area(): float { return $this->l * $this->w; }
public function name(): string { return "Rectangle"; }
public function printDetails(): void
{
echo $this->name() . " area = " . $this->area() . " sq " . Shape::UNITS . "<br>";
}
}
$shapes = [new Circle(3), new Rectangle(4, 5)];
foreach ($shapes as $s) {
$s->printDetails();
}
var_dump($shapes[0] instanceof Shape);
?>Circle area = 28.27 sq cm Rectangle area = 20 sq cm bool(true)
| Feature | Abstract class | Interface |
|---|---|---|
| Instantiation | not possible | not possible |
| Method bodies | may have normal (concrete) methods | no bodies (only signatures) |
| Properties | yes | no (constants only) |
| Inheritance | a class extends one | a class implements many |
| Keyword | extends | implements |
Traits and final
A trait is a group of methods that can be copied into several unrelated classes with use. It provides code reuse without inheritance. The keyword final prevents a method from being overridden, or a class from being extended.
<?php
trait Loggable
{
public function log(string $msg): void
{
echo "[" . static::class . "] $msg<br>";
}
}
final class Invoice
{
use Loggable;
final public function total(): int { return 1180; }
}
class User
{
use Loggable;
}
(new Invoice())->log("Invoice created, total " . (new Invoice())->total());
(new User())->log("User logged in");
?>[Invoice] Invoice created, total 1180 [User] User logged in
Overloading
Overloading in PHP provides a means to dynamically "create" properties and methods. These dynamic entities are processed via magic methods that you define in the class. The overloading methods are invoked when interacting with properties or methods that have not been declared or are not visible in the current scope – called "inaccessible properties" and "inaccessible methods" below. All overloading methods must be defined as public.
PHP's meaning of "overloading" differs from most object-oriented languages, where overloading means several methods with the same name but different numbers or types of parameters. None of the arguments of these magic methods can be passed by reference.
Property overloading
public __set(string $name, mixed $value): void public __get(string $name): mixed public __isset(string $name): bool public __unset(string $name): void
__set()runs when writing data to inaccessible properties.__get()is used for reading data from inaccessible properties.__isset()is triggered by callingisset()orempty()on inaccessible properties.__unset()is invoked whenunset()is used on inaccessible properties.
The $name argument is the name of the property being accessed; the $value argument of __set() is the value being assigned. Property overloading works only in object context – these methods are not triggered in static context and must not be declared static.
<?php
class PropertyTest
{
/** Location for overloaded data. */
private $data = array();
/** Overloading not used on declared properties. */
public $declared = 1;
/** Overloading only used on this when accessed outside the class. */
private $hidden = 2;
public function __set($name, $value)
{
echo "Setting '$name' to '$value'\n";
$this->data[$name] = $value;
}
public function __get($name)
{
echo "Getting '$name'\n";
if (array_key_exists($name, $this->data)) {
return $this->data[$name];
}
$trace = debug_backtrace();
trigger_error('Undefined property via __get(): ' . $name .
' in ' . basename($trace[0]['file']) . ' on line ' . $trace[0]['line'], E_USER_NOTICE);
return null;
}
public function __isset($name)
{
echo "Is '$name' set?\n";
return isset($this->data[$name]);
}
public function __unset($name)
{
echo "Unsetting '$name'\n";
unset($this->data[$name]);
}
/** Not a magic method, just here for example. */
public function getHidden()
{
return $this->hidden;
}
}
echo "<pre>\n";
$obj = new PropertyTest;
$obj->a = 1;
echo $obj->a . "\n\n";
var_dump(isset($obj->a));
unset($obj->a);
var_dump(isset($obj->a));
echo "\n";
echo $obj->declared . "\n\n";
echo "Let's experiment with the private property named 'hidden':\n";
echo "Privates are visible inside the class, so __get() not used...\n";
echo $obj->getHidden() . "\n";
echo "Privates not visible outside of class, so __get() is used...\n";
echo $obj->hidden . "\n";
echo "</pre>";
?>Setting 'a' to '1' Getting 'a' 1 Is 'a' set? bool(true) Unsetting 'a' Is 'a' set? bool(false) 1 Let's experiment with the private property named 'hidden': Privates are visible inside the class, so __get() not used... 2 Privates not visible outside of class, so __get() is used... Getting 'hidden' Notice: Undefined property via __get(): hidden in propoverload.php on line 56 in /opt/lampp/htdocs/phpbook/ch18/propoverload.php on line 23
Method overloading
public __call(string $name, array $arguments): mixed public static __callStatic(string $name, array $arguments): mixed
__call() is triggered when invoking inaccessible methods in an object context; __callStatic() when invoking inaccessible methods in a static context. $name is the method name; $arguments is an array of the parameters passed.
<?php
class MethodTest
{
public function __call($name, $arguments)
{
// Note: value of $name is case sensitive.
echo "Calling object method '$name' " . implode(', ', $arguments) . "<br>";
}
public static function __callStatic($name, $arguments)
{
echo "Calling static method '$name' " . implode(', ', $arguments) . "<br>";
}
}
$obj = new MethodTest;
$obj->runTest('in object context');
MethodTest::runTest('in static context');
?>Calling object method 'runTest' in object context Calling static method 'runTest' in static context
__toString()
__toString() decides what is printed when an object is used as a string:
<?php
class Money
{
public function __construct(private float $amount) {}
public function __toString(): string
{
return "Rs. " . number_format($this->amount, 2);
}
}
$price = new Money(2499.5);
echo "The price is $price";
?>The price is Rs. 2,499.50
Object Iteration
PHP provides a way for objects to be iterated through with, for example, a foreach statement. By default, all visible properties are used for the iteration.
<?php
class MyClass
{
public $var1 = 'value 1';
public $var2 = 'value 2';
public $var3 = 'value 3';
protected $protected = 'protected var';
private $private = 'private var';
function iterateVisible()
{
echo "MyClass::iterateVisible:<br>";
foreach ($this as $key => $value) {
print "$key => $value<br>";
}
}
}
$class = new MyClass();
foreach ($class as $key => $value) {
print "$key => $value<br>";
}
echo "<br>";
$class->iterateVisible();
?>var1 => value 1 var2 => value 2 var3 => value 3 MyClass::iterateVisible: var1 => value 1 var2 => value 2 var3 => value 3 protected => protected var private => private var
As the output shows, foreach iterates through all visible properties: from outside only the public ones, from inside also protected and private. To go a step further, implement PHP's internal Iterator interface; the object then decides what is iterated and how.
<?php
class MyIterator implements Iterator
{
private $var = array();
public function __construct($array)
{
if (is_array($array)) {
$this->var = $array;
}
}
public function rewind(): void
{
echo "rewinding<br>";
reset($this->var);
}
public function current(): mixed
{
$var = current($this->var);
echo "current: $var<br>";
return $var;
}
public function key(): mixed
{
$var = key($this->var);
echo "key: $var<br>";
return $var;
}
public function next(): void
{
$var = next($this->var);
echo "next: $var<br>";
}
public function valid(): bool
{
$key = key($this->var);
$var = ($key !== NULL && $key !== FALSE);
echo "valid: " . ($var ? "true" : "false") . "<br>";
return $var;
}
}
$values = array(1, 2, 3);
$it = new MyIterator($values);
foreach ($it as $a => $b) {
print "$a: $b<br>";
}
?>rewinding valid: true current: 1 key: 0 0: 1 next: 2 valid: true current: 2 key: 1 1: 2 next: 3 valid: true current: 3 key: 2 2: 3 next: valid: false
PHP 8.1 change. The methods of PHP's built-in interfaces now declare return types (current(): mixed, next(): void, valid(): bool …). Implementations without these return types cause a Deprecated notice, so the old example has been updated with them.
You can also define your class so that it does not have to implement all Iterator methods, by implementing the IteratorAggregate interface. Its single method getIterator() returns an iterator – here the MyIterator class above, saved in the file iterator_class.php:
<?php
require "iterator_class.php";
class MyCollection implements IteratorAggregate
{
private $items = array();
private $count = 0;
// Required definition of interface IteratorAggregate
public function getIterator(): Iterator
{
return new MyIterator($this->items);
}
public function add($value)
{
$this->items[$this->count++] = $value;
}
}
$coll = new MyCollection();
$coll->add('value 1');
$coll->add('value 2');
$coll->add('value 3');
foreach ($coll as $key => $val) {
echo "key/value: [$key -> $val]<br><br>";
}
?>rewinding valid: true current: value 1 key: 0 key/value: [0 -> value 1] next: value 2 valid: true current: value 2 key: 1 key/value: [1 -> value 2] next: value 3 valid: true current: value 3 key: 2 key/value: [2 -> value 3] next: valid: false
Design Patterns
Patterns are ways to describe best practices and good designs. They show flexible solutions to common programming problems.
Factory
The Factory pattern allows objects to be instantiated at run time. It is called a factory because it is responsible for "manufacturing" objects. A parameterised factory receives the name of the class to instantiate as an argument.
<?php
class Driver_MySQL
{
public function name() { return "MySQL driver ready"; }
}<?php
class Example
{
// The parameterized factory method
public static function factory($type)
{
if (include_once 'Drivers/' . $type . '.php') {
$classname = 'Driver_' . $type;
return new $classname;
} else {
throw new Exception('Driver not found');
}
}
}
// Load a MySQL Driver
$mysql = Example::factory('MySQL');
// Load an SQLite Driver
$sqlite = Example::factory('SQLite');
echo $mysql->name() . "<br>";
echo $sqlite->name();
?>MySQL driver ready SQLite driver ready
Defining this method in a class allows drivers to be loaded on the fly. If Example were a database abstraction class, loading a MySQL or an SQLite driver is a single call.
Singleton
The Singleton pattern applies when there must be exactly one instance of a class. The most common example is a database connection. Implementing this pattern makes the single instance easily accessible to many other objects.
<?php
class Example
{
// Hold an instance of the class
private static $instance;
// A private constructor; prevents direct creation of object
private function __construct()
{
echo 'I am constructed<br>';
}
// The singleton method
public static function singleton()
{
if (!isset(self::$instance)) {
$c = __CLASS__;
self::$instance = new $c;
}
return self::$instance;
}
// Example method
public function bark()
{
echo 'Woof!<br>';
}
// Prevent users from cloning the instance
private function __clone() {}
}
$a = Example::singleton();
$b = Example::singleton(); // constructor is NOT called again
$a->bark();
var_dump($a === $b);
echo "<br>";
try {
$c = new Example(); // not allowed
} catch (Error $e) {
echo "Error: " . $e->getMessage();
}
?>I am constructed Woof! bool(true) Error: Call to private Example::__construct() from global scope
The constructor runs only once; every later call of singleton() returns the same object. Making __construct() and __clone() private prevents other code from creating or copying instances.
Key Takeaways
- Abstract classes cannot be instantiated; abstract methods must be implemented by child classes.
- Interfaces declare public methods only; a class can implement many interfaces.
- Traits copy methods into classes with
use;finalblocks overriding/extending. __get,__set,__isset,__unset,__calland__callStatichandle inaccessible members (PHP "overloading").foreachiterates visible properties;IteratorandIteratorAggregatecustomise iteration (with PHP 8.1 return types).- Factory creates objects of a class chosen at run time; Singleton guarantees one instance.
Program File
Extract the ZIP into C:\xampp\htdocs. It creates the folder phpbook/ch18 used in the file labels above; open the programs at http://localhost/phpbook/… with Apache (and MySQL, where needed) started in the XAMPP Control Panel.
Question / Answer (15 questions)
MCQ Test : Check Your Understanding (5 questions)
Select an option and press Check Answer, or answer all questions and press Submit Test to see your score. Each answer comes with an explanation.
Bibliography/References
The notes on this page are the author’s own compilation from the e-book PHP: Basics to Advance. Every program was run on Apache with PHP 8 and MariaDB, and the output shown is the real result of that run. The works listed below were consulted and are cited for attribution and further reading. Product names and trademarks belong to their respective owners.
- The PHP Group. (n.d.). PHP Manual – Class Abstraction. https://www.php.net/manual/en/language.oop5.abstract.php
- The PHP Group. (n.d.). PHP Manual – Object Interfaces. https://www.php.net/manual/en/language.oop5.interfaces.php
- The PHP Group. (n.d.). PHP Manual – Traits. https://www.php.net/manual/en/language.oop5.traits.php
- The PHP Group. (n.d.). PHP Manual – Overloading. https://www.php.net/manual/en/language.oop5.overloading.php
- The PHP Group. (n.d.). PHP Manual – Object Iteration. https://www.php.net/manual/en/language.oop5.iterations.php
- Gamma, E., Helm, R., Johnson, R., & Vlissides, J. (1994). Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley.