Interfaces

An interface is an OOP element that groups a set of function declarations without implementing them, that is, it specifies the name, return type, and arguments, but not the block of code. Interfaces are different from abstract classes, since they cannot contain any implementation at all, whereas abstract classes could mix both method definitions and implemented ones. The purpose of interfaces is to state what a class can do, but not how it is done.

From our code, we can identify a potential usage of interfaces. Customers have an expected behavior, but its implementation changes depending on the type of customer. So, Customer could be an interface instead of an abstract class. But as an interface cannot implement any function, nor can it contain properties, we will have to move the concrete code from the Customer class to somewhere else. For now, let's move it up to the Person class. Edit the Person class as shown:

<?php

namespace BookstoreDomain;

class Person {

    private static $lastId = 0;
    protected $id;
    protected $firstname;
    protected $surname;
    protected $email;

    public function __construct(
        int $id,
        string $firstname,
        string $surname,
        string $email
    ) {
        $this->firstname = $firstname;
        $this->surname = $surname;
        $this->email = $email;

        if (empty($id)) {
            $this->id = ++self::$lastId;
        } else {
            $this->id = $id;
            if ($id > self::$lastId) {
                self::$lastId = $id;
            }
        }
    }

    public function getFirstname(): string {
        return $this->firstname;
    }
    public function getSurname(): string {
        return $this->surname;
    }
    public static function getLastId(): int {
        return self::$lastId;
    }
    public function getId(): int {
        return $this->id;
    }
    public function getEmail(): string {
        return $this->email;
    }
}

Note

Complicating things more than necessary

Interfaces are very useful, but there is always a place and a time for everything. As our application is very simple due to its didactic nature, there is no real place for them. The abstract class already defined in the previous section is the best approach for our scenario. But just for the sake of showing how interfaces work, we will be adapting our code to them.

Do not worry though, as most of the code that we are going to introduce now will be replaced by better practices once we introduce databases and the MVC pattern in Chapter 5, Using Databases, and Chapter 6, Adapting to MVC.

When writing your own applications, do not try to complicate things more than necessary. It is a common pattern to see very complex code from developers that try to show up all the skills they have in a very simple scenario. Use only the necessary tools to leave clean code that is easy to maintain, and of course, that works as expected.

Change the content of Customer.php with the following:

<?php

namespace BookstoreDomain;

interface Customer {
    public function getMonthlyFee(): float;
    public function getAmountToBorrow(): int;
    public function getType(): string;
}

Note that an interface is very similar to an abstract class. The differences are that it is defined with the keyword interface, and that its methods do not have the word abstract. Interfaces cannot be instantiated, since their methods are not implemented as with abstract classes. The only thing you can do with them is make a class to implement them.

Implementing an interface means implementing all the methods defined in it, like when we extended an abstract class. It has all the benefits of the extension of abstract classes, such as belonging to that type—useful when type hinting. From the developer's point of view, using a class that implements an interface is like writing a contract: you ensure that your class will always have the methods declared in the interface, regardless of the implementation. Because of that, interfaces only care about public methods, which are the ones that other developers can use. The only change you need to make in your code is to replace the keywords extends by implements:

class Basic implements Customer {

So, why would someone use an interface if we could always use an abstract class that not only enforces the implementation of methods, but also allows inheriting code as well? The reason is that you can only extend from one class, but you can implement multiple instances at the same time. Imagine that you had another interface that defined payers. This could identify someone that has the ability to pay something, regardless of what it is. Save the following code in src/Domain/Payer.php:

<?php

namespace BookstoreDomain;

interface Payer {
    public function pay(float $amount);
    public function isExtentOfTaxes(): bool;
}

Now our basic and premium customers can implement both the interfaces. The basic customer will look like the following:

//...
use BookstoreDomainCustomer; 
use BookstoreDomainPerson;

class Basic extends Person implements Customer {
    public function getMonthlyFee(): float {
//...

And the premium customer will change in the same way:

//...
use BookstoreDomainCustomer; 
use BookstoreDomainPerson;

class Premium extends Person implements Customer {
    public function getMonthlyFee(): float {
//...

You should see that this code would no longer work. The reason is that although we implement a second interface, the methods are not implemented. Add these two methods to the basic customer class:

public function pay(float $amount) {
    echo "Paying $amount.";
}

public function isExtentOfTaxes(): bool {
    return false;
}

Add these two methods to the premium customer class:

public function pay(float $amount) {
    echo "Paying $amount.";
}

public function isExtentOfTaxes(): bool {
    return true;
}

If you know that all customers will have to be payers, you could even make the Customer interface to inherit from the Payer interface:

interface Customer extends Payer {

This change does not affect the usage of our classes at all. Other developers will see that our basic and premium customers inherit from Payer and Customer, and so they contain all the necessary methods. That these interfaces are independent, or they extend from each other is something that will not affect too much.

Interfaces can only extend from other interfaces, and classes can only extend from other classes. The only way to mix them is when a class implements an interface, but neither does a class extend from an interface, nor does an interface extend from a class. But from the point of view of type hinting, they can be used interchangeably.

To summarize this section and make things clear, let's show what the hierarchic tree looks like after all the new additions. As in abstract classes, the methods declared in an interface are shown in the interface rather than in each of the classes that implement it.

Interfaces

Polymorphism

Polymorphism is an OOP feature that allows us to work with different classes that implement the same interface. It is one of the beauties of object-oriented programming. It allows the developer to create a complex system of classes and hierarchic trees, but offers a simple way of working with them.

Imagine that we have a function that, given a payer, checks whether it is exempt of taxes or not, and makes it pay some amount of money. This piece of code does not really mind if the payer is a customer, a librarian, or someone who has nothing to do with the bookstore. The only thing that it cares about is that the payer has the ability to pay. The function could be as follows:

function processPayment(Payer $payer, float $amount) {
    if ($payer->isExtentOfTaxes()) {
        echo "What a lucky one...";
    } else {
        $amount *= 1.16;
    }
    $payer->pay($amount);
}

You could send basic or premium customers to this function, and the behavior will be different. But, as both implement the Payer interface, both objects provided are valid types, and both are capable of performing the actions needed.

The checkIfValid function takes a customer and a list of books. We already saw that sending any kind of customer makes the function work as expected. But what happens if we send an object of the class Librarian, which extends from Payer? As Payer does not know about Customer (it is rather the other way around), the function will complain as the type hinting is not accomplished.

One useful feature that comes with PHP is the ability to check whether an object is an instance of a specific class or interface. The way to use it is to specify the variable followed by the keyword instanceof and the name of the class or interface. It returns a Boolean, which is true if the object is from a class that extends or implements the specified one, or false otherwise. Let's see some examples:

$basic = new Basic(1, "name", "surname", "email");
$premium = new Premium(2, "name", "surname", "email");
var_dump($basic instanceof Basic); // true
var_dump($basic instanceof Premium); // false
var_dump($premium instanceof Basic); // false
var_dump($premium instanceof Premium); // true
var_dump($basic instanceof Customer); // true
var_dump($basic instanceof Person); // true
var_dump($basic instanceof Payer); // true

Remember to add all the use statements for each of the class or interface, otherwise PHP will understand that the specified class name is inside the namespace of the file.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.215.96