Declare final as best practice

The final keyword in PHP is used to declare that a class cannot be overridden by a sub-class. This stops other developers from using your classes in a way that you had not planned for. If someone tries to override your final class, they will get a fatal error message that the class can not be override because of the final keyword.

Why should we declare all our classes final by default?

There are multiple reasons for this. But the most important is that we want to stay in control and find the best solutions first. Defining our classes as final is a little trick to let developers think more about finding better solutions and makes them think more about how their code works. When they have a situation that they think they need to extend this class, but they can’t because of the final keyword. Then they will start thinking about solutions. Developers are problem solvers. They start thinking, hmm… maybe I can use composition instead of inheritance? Its a good rule of thump to use composition over inheritance which is sometimes forgotten by developers. So we force them to think; Can we find  in a solution using composition?

In the following example you see an example of inheritance. Because the class was not defined as final, this was possible.

<?php
class OrderService implements OrderServiceInterface
{
    public function createOrder()
    {
        /* Do some order creation magic */
    }
}

class NotifyOrderService extends OrderService
{
    public function createOrder()
    {
        $order = parent::createOrder();
        $this->notifyOrderCreated($order);
    }
}

But what happens if we define the class as final. Well the previous solution did not work so the developer thought to himself. How can I fix this? Well lets try composition instead.

<?php
final class OrderService implements OrderServiceInterface
{
    public function createOrder()
    {
        /* Do some order creation magic */
    }
}

class NotifyOrderService implements OrderServiceInterface
{
    private $orderService;
    
    public function __construct(OrderServiceInterface $orderService)
    {
        $this->orderService = $orderService;
    }

    public function createOrder()
    {
        $order = $this->orderService->createOrder();
        $this->notifyOrderCreated($order);
    }
}

By denying inheritance the mistake of using inheritance in this case is avoided. The developer was forced to think and select a better solution.

But what if I really need inheritance?

Then developers should ask themselves if they really thought this trough. Why can’t I write a customized implementation? Why can’t I use composition? 

You need to remember that if you need to remove final from an implementation then there is some kind of code smell. Why are you not creating a new implementation instead?

But if you are really determined to remove the final class in a situation were you don’t have abstractions. Then this should be discussed with the team first. The developer should explain to the team why they think that they need inheritance. Discussions like this will allow the team to find better solutions. You are stronger as a team then individually. Remember that. Not everyone sees things from the same perspectives. Removing a final keyword should be a last resort if there is no other way. And this should be a team decision.

Are there situation to not use final by default?

It might be said that if there is no abstraction or interface that its not smart to define it as final. This is because the only way to extend the class is to use inheritance. But I don’t agree with this as a good reason for that. If you made a class that does not use abstractions then it was never intended to be extended. If you intend to extend something in the future then you should have made abstractions first. Or at least add abstractions when you need to extend that class. We should be flexible in finding the best solutions. Any class that needs extension and have multiple implementations should start from abstractions. No one said that you can’t add abstractions later. The downside is it might require some refactoring, but this makes you think about not using abstractions in the future.

Conclussion

So what should you remember? Use final by default for every single class you create. Don’t start adding it to existing code without discussing with the team or thinking it trough. But new code should start from final. If anyone tries to extend your classes and complains. Then you start knowing what other developers are actually doing with your classes. You start discussions to improve the quality of the code base. You can still make guidelines about when to use final classes and when not. But a good practice is to always start from final first. Start as strict as possible and then loose up if required. This make us more aware of what we are actually doing and will improve the quality of our code. As always, think before you code!
I would like to hear your thoughts and experiences about this.