6. Interfaces
An interface is a structure that defines method prototypes. A class that implements an interface must implement the code for all of the interface’s methods. An interface is a contract: it specifies what an object can do, without specifying how it does it.
6.1. The script directory structure
The scripts for this chapter are in the [scripts-console/interfaces] folder:
6.2. A First Interface
We define an interface named [IExample] in a script named [IExample.php]. In the same script, we define two classes that implement the [IExample] interface:
Comments
- Lines 10–15: The [IExample] interface defines two methods: the [add] function (line 13) and the [subtract] function (line 15). The interface does not define the code for these methods. The classes that implement the interface will do so. The methods of an interface must be public;
- line 19: the class [Class1] implements ([implements]) the interface [IExample]. It therefore defines code for the methods [add] (line 21) and [subtract] (line 26). The parameter names may differ from those in the interface ([$a] instead of [$i]), but their types must be compatible. Note, however: with the named arguments of PHP 8, a call to [add(i: 1, j: 2)] would not work with [Class1]. It is therefore preferable to keep the interface names;
- Lines 33–44: Same applies to class [Class2].
We use the [IExample] interface in the following [interface-01.php] script:
Results:
Comments
- lines 7–10: inclusion and declaration of the classes and interfaces used by the script. The file [IExample.php] is included using the statement [require_once]: it will be included only once, even if multiple scripts in the project include it;
- Lines 13–17: A function is defined;
- Line 13: Here, we have declared the types of the three parameters. If, at runtime, the actual parameter is not of the expected type, a [TypeError] error is thrown. The third parameter is of type [IExample]. This means that the actual parameter must be an instance of a class that implements the [IExample] interface; therefore, in our example, an instance of the classes [Class1] or [Class2]. The function [calculate] is not aware of these classes; it only recognizes the interface. We can pass it objects of classes written later, provided they implement the [IExample] interface;
- lines 15–16: we use the [add, subtract] methods of the [IExample] interface;
- line 24: the third actual parameter is of type [Class1], which is compatible with the type [IExample] of the function’s third formal parameter;
- line 25: same applies to the class [Class2];
- line 28: an interface cannot be instantiated; an error is returned (line 6 of the results).
6.3. Deriving an Interface
Just as a child class can extend a parent class, a child interface can extend a parent interface.
Consider the interface [IExample2] defined in the file [IExample2.php]:
Comments
- line 7: the [IExample2] interface will be in the same namespace as the [IExample] interface;
- line 10: the definition file for the [IExample] interface is included;
- Lines 13–17: define the [IExample2] interface, which extends ([extends]) the [IExample] interface and adds a new method to it (line 16). An interface can even extend multiple interfaces: [interface I3 extends I1, I2];
- lines 20–28: The class [Class3] extends the class [Class2]. Since the latter implements the [IExample] interface, the [Class3] class will also do so. In fact, [Class3] inherits all the public and protected methods of the [Class2] class, including the [add] and [subtract] methods. Furthermore, it is specified that [Class3] implements the [IExample2] interface. As such, it must also implement the [multiply] method, which [Class2] does not do. This is what lines 24–27 do;
- line 23: Starting with PHP 8.3, the attribute [#[\Override]], discussed in the previous chapter, also applies to a method that implements an interface method. If the method [multiply] were to disappear from the interface [IExample2], PHP would signal this.
The [IExample2] interface is used by the following [interface-02.php] script:
Results:
Comments
- Line 12: The third parameter of the [calculate] function is of type [IExample2];
- Lines 14–16: The three methods of the [IExample2] interface are used: the two inherited from [IExample] and its own;
- line 23: the function [calculate] is called with, as its third parameter, an object of type [Class3] that implements [IExample2] (line 21).
6.4. Passing an Interface as a Function Parameter
We saw in the previous chapter (in the section “Passing an Object as a Function Parameter”) that when the expected type for a function parameter is a class, the actual parameter can be an object of the expected type or a derived type. The same applies to interfaces, as shown in the following script, [interface-03.php]:
Results:
Comments
- Line 15: The function [calculate] expects a type [IExample] as its third parameter. This means that the actual parameter can be of type [IExample] or a derived type;
- Line 23: An object of type [$c3] is instantiated from the class [Class3], which implements the interface [IExample2], which in turn extends the interface [IExample]. So ultimately, [$c3] implements the [IExample] interface;
- Line 25: The function [calculate] is called with the object [$c3] (of type [Class3]) as its third parameter. Through class inheritance and implementations, we saw that the object [$c3] implements the type [IExample]. We can therefore use it as the third parameter;
- lines 27–30: The [instanceof] operator checks whether an object implements an interface. An object of type [Class1] implements [IExample] but not [IExample2] (line 4 of the results). An object of type [Class3] implements both (line 5);
- line 32: The [class_implements] function returns the list of interfaces implemented by an object or class, either directly or through inheritance (lines 6–10 of the results).
6.5. Interface constants, multiple interfaces, and intersection types
An interface can also declare constants, and a class can implement multiple interfaces. We define two interfaces, [ICalculation] and [INameable], and two classes that implement them in the file [ICalculation.php]:
Comments
- line 13: The [ICalculation] interface declares a constant. All classes that implement the interface inherit it. Starting with PHP 8.3, a class or interface constant can be typed (here, [int]);
- Lines 19–22: A second interface, with a single method;
- line 25: the class [Addition] implements both interfaces: the interface names are separated by commas. It must define the methods of both;
- line 30: the constant inherited from the interface is accessed by [self::PRECISION]. The function [round] rounds a number to the specified number of decimal places;
- line 46: The constant can also be referred to by the interface name: [ICalculation::PRECISION].
These interfaces are used by the script [interface-04.php]:
Results:
Comments
- Line 14: The third parameter of the [execute] function is of type [ICalculation] and has a default value—a [Addition] object created with [new]. Up to PHP 8.0, the default value of a parameter could only be a constant. Starting with PHP 8.1, the expression [new] is accepted in parameter default values (“new in initializers”), as well as in the arguments of PHP attributes ([#[...]]), static variables, and global constants. It remains prohibited in the default value of a class attribute. The object is created on every call that omits the parameter;
- line 20: the type [ICalculation&INameable] is an intersection type (PHP 8.1): the actual parameter must implement both [ICalculation] and [INameable]. The function can then call methods from both interfaces. This should not be confused with the union type [ICalculation|INameable] (either one);
- line 27: the constant is accessible from outside via the interface name (line 2 of the results);
- line 29: the third parameter is omitted; it is a [Addition] object that performs the calculation (line 3);
- line 31: a [Division] object is explicitly passed (line 4). Note that the integers 10 and 3 are accepted for parameters of type [float] despite the declaration [strict_types=1]: the conversion of a
***[int]to a[float]*** is the only automatic conversion allowed in strict mode; - Lines 33–34: Both classes implement both interfaces; they are accepted by the [display] function (lines 5–6);
- line 36: the notation [Division::class] represents the full class name, in this case [Examples\Division]. The function [class_implements] accepts both a class name and an object.
Interfaces are ubiquitous in PHP itself. We have already encountered two in the previous chapter: [Stringable], automatically implemented by any class with a [__toString] method, and [JsonSerializable], which allows you to control an object’s JSON encoding. In the next chapter, we’ll look at the [Throwable] interface, which is implemented by all errors and exceptions. An enumeration can also implement an interface. Finally, we’ll see later in this course that interfaces form the basis of our applications’ layered architecture: each layer knows only the interface of the adjacent layer.