Inheritance

We have presented the object-oriented paradigm as the panacea for complex data structures, and even though we have shown that we can define objects with properties and methods, and it looks pretty and fancy, it is not something that we could not solve with arrays. Encapsulation was one feature that made objects more useful than arrays, but their true power lies in inheritance.

Introducing inheritance

Inheritance in OOP is the ability to pass the implementation of the class from parents to children. Yes, classes can have parents, and the technical way of referring to this feature is that a class extends from another class. When extending a class, we get all the properties and methods that are not defined as private, and the child class can use them as if they were its own. The limitation is that a class can only extend from one parent.

To show an example, let's consider our Customer class. It contains the properties firstname, surname, email, and id. A customer is actually a specific type of person, one that is registered in our system, so he/she can get books. But there can be other types of persons in our system, like librarian or guest. And all of them would have some common properties to all people, that is, firstname and surname. So it would make sense if we create a Person class, and make the Customer class extend from it. The hierarchic tree would look as follows:

Introducing inheritance

Note how Customer is connected to Person. The methods in Person are not defined in Customer, as they are implicit from the extension. Now save the new class in src/Domain/Person.php, following our convention:

<?php

namespace BookstoreDomain;

class Person {
    protected $firstname;
    protected $surname;

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

    public function getFirstname(): string {
        return $this->firstname;
    }

    public function getSurname(): string {
        return $this->surname;
    }
}

The class defined in the preceding code snippet does not look special; we have just defined two properties, a constructor and two getters. Note though that we defined the properties as protected, because if we defined them as private, the children would not be able to access them. Now we can update our Customer class by removing the duplicate properties and its getters:

<?php

namespace BookstoreDomain;

class Customer extends Person {
    private static $lastId = 0;
    private $id;
    private $email;

    public function __construct(
        int $id,
        string $name,
        string $surname,
        string $email
    ) {
        if (empty($id)) {
            $this->id = ++self::$lastId;
        } else {
            $this->id = $id;
            if ($id > self::$lastId) {
                self::$lastId = $id;
            }
        }
        $this->name = $name;
        $this->surname = $surname;
        $this->email = $email;
    }

    public static function getLastId(): int {
        return self::$lastId;
    }

    public function getId(): int {
        return $this->id;
    }

    public function getEmail(): string {
        return $this->email;
    }

    public function setEmail($email): string {
        $this->email = $email;
    }
}

Note the new keyword extends; it tells PHP that this class is a child of the Person class. As both Person and Customer are in the same namespace, you do not have to add any use statement, but if they were not, you should let it know how to find the parent. This code works fine, but we can see that there is a bit of duplication of code. The constructor of the Customer class is doing the same job as the constructor of the Person class! We will try to fix it really soon.

In order to reference a method or property of the parent class from the child, you can use $this as if the property or method was in the same class. In fact, you could say it actually is. But PHP allows you to redefine a method in the child class that was already present in the parent. If you want to reference the parent's implementation, you cannot use $this, as PHP will invoke the one in the child. To force PHP to use the parent's method, use the keyword parent:: instead of $this. Update the constructor of the Customer class as follows:

public function __construct(
    int $id,
    string $firstname,
    string $surname,
    string $email
) {
    parent::__construct($firstname, $surname);
    if (empty($id)) {
        $this->id = ++self::$lastId;
    } else {
        $this->id = $id;
        if ($id > self::$lastId) {
            self::$lastId = $id;
        }
    }
    $this->email = $email;
}

This new constructor does not duplicate code. Instead, it calls the constructor of the parent class Person, sending $firstname and $surname, and letting the parent do what it already knows how to do. We avoid code duplication and, on top of that, we make it easier for any future changes to be made in the constructor of Person. If we need to change the implementation of the constructor of Person, we will change it in one place only, instead of in all the children.

Overriding methods

As said before, when extending from a class, we get all the methods of the parent class. That is implicit, so they are not actually written down inside the child's class. What would happen if you implement another method with the same signature and/or name? You will be overriding the method.

As we do not need this feature in our classes, let's just add some code in our init.php file to show this behavior, and then you can just remove it. Let's define a class Pops, a class Child that extends from the parent, and a sayHi method in both of them:

class Pops {
    public function sayHi() {
        echo "Hi, I am pops.";
    }
}

class Child extends Pops{
    public function sayHi() {
        echo "Hi, I am a child.";
    }
}

$pops = new Pops();
$child = new Child();
echo $pops->sayHi(); // Hi, I am pops.
echo $child->sayHi(); // Hi, I am Child.

The highlighted code shows you that the method has been overridden, so when invoking it from a child's point of view, we will be using it rather than the one inherited from its father. But what happens if we want to reference the inherited one too? You can always reference it with the keyword parent. Let's see how it works:

class Child extends Pops{
    public function sayHi() {
        echo "Hi, I am a child.";
        parent::sayHi();
    }
}

$child = new Child();
echo $child->sayHi(); // Hi, I am Child. Hi I am pops.

Now the child is saying hi for both himself and his father. It seems very easy and handy, right? Well, there is a restriction. Imagine that, as in real life, the child was very shy, and he would not say hi to everybody. We could try to set the visibility of the method as protected, but see what happens:

class Child extends Pops{
    protected function sayHi() {
        echo "Hi, I am a child.";
    }
}

When trying this code, even without trying to instantiate it, you will get a fatal error complaining about the access level of that method. The reason is that when overriding, the method has to have at least as much visibility as the one inherited. That means that if we inherit a protected one, we can override it with another protected or a public one, but never with a private one.

Abstract classes

Remember that you can extend only from one parent class each time. That means that Customer can only extend from Person. But if we want to make this hierarchic tree more complex, we can create children classes that extend from Customer, and those classes will extend implicitly from Person too. Let's create two types of customer: basic and premium. These two customers will have the same properties and methods from Customer and from Person, plus the new ones that we implement in each one of them.

Save the following code as src/Domain/Customer/Basic.php:

<?php

namespace BookstoreDomainCustomer;

use BookstoreDomainCustomer;

class Basic extends Customer {
    public function getMonthlyFee(): float {
        return 5.0;
    }

    public function getAmountToBorrow(): int {
        return 3;
    }

    public function getType(): string {
        return 'Basic';
    }
}

And the following code as src/Domain/Customer/Premium.php:

<?php

namespace BookstoreDomainCustomer;

use BookstoreDomainCustomer;

class Premium extends Customer {
    public function getMonthlyFee(): float {
        return 10.0;
    }

    public function getAmountToBorrow(): int {
        return 10;
    }

    public function getType(): string {
        return 'Premium';
    }
}

Things to note in the preceding two codes are that we extend from Customer in two different classes, and it is perfectly legal— we can extend from classes in different namespaces. With this addition, the hierarchic tree for Person would look as follows:

Abstract classes

We define the same methods in these two classes, but their implementations are different. The aim of this approach is to use both types of customers indistinctively, without knowing which one it is each time. For example, we could temporally have the following code in our init.php. Remember to add the use statement to import the class Customer if you do not have it.

function checkIfValid(Customer $customer, array $books): bool {
    return $customer->getAmountToBorrow() >= count($books);
}

The preceding function would tell us if a given customer could borrow all the books in the array. Notice that the type hinting of the method says Customer, without specifying which one. This will accept objects that are instances of Customer or any class that extends from Customer, that is, Basic or Premium. Looks legit, right? Let's try to use it then:

$customer1 = new Basic(5, 'John', 'Doe', '[email protected]');
var_dump(checkIfValid($customer1, [$book1])); // ok
$customer2 = new Customer(7, 'James', 'Bond', '[email protected]');
var_dump(checkIfValid($customer2, [$book1])); // fails

The first invocation works as expected, but the second one fails, even though we are sending a Customer object. The problem arises because the parent does not know about any getAmountToBorrow method! It also looks dangerous that we rely on the children to always implement that method. The solution lies in using abstract classes.

An abstract class is a class that cannot be instantiated. Its sole purpose is to make sure that its children are correctly implemented. Declaring a class as abstract is done with the keyword abstract, followed by the definition of a normal class. We can also specify the methods that the children are forced to implement, without implementing them in the parent class. Those methods are called abstract methods, and are defined with the keyword abstract at the beginning. Of course, the rest of the normal methods can stay there too, and will be inherited by its children:

<?php
abstract class Customer extends Person {
//...
    abstract public function getMonthlyFee();
    abstract public function getAmountToBorrow();
    abstract public function getType();
//...
}

The preceding abstraction solves both problems. First, we will not be able to send any instance of the class Customer, because we cannot instantiate it. That means that all the objects that the checkIfValid method is going to accept are only the children from Customer. On the other hand, declaring abstract methods forces all the children that extend the class to implement them. With that, we make sure that all objects will implement getAmountToBorrow, and our code is safe.

The new hierarchic tree will define the three abstract methods in Customer, and will omit them for its children. It is true that we are implementing them in the children, but as they are enforced by Customer, and thanks to abstraction, we are sure that all classes extending from it will have to implement them, and that it is safe to do so. Let's see how this is done:

Abstract classes

With the last new addition, your init.php file should fail. The reason is that it is trying to instantiate the class Customer, but now it is abstract, so you cannot. Instantiate a concrete class, that is, one that is not abstract, to solve the problem.

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

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