Don’t use properties and methods from outside the interface

Don't use methods from outside the interface - featured image

It may sound obvious or even weird for people who programming in statically typed or compiled languages. In my work, I use PHP which is a dynamically typed language with optional strict typing introduced in version 7. At the beginning of my journey with PHP, I didn’t care so much about typing. I had a trivial cause — they didn’t exist yet.

I used to write a code without thinking about types. It was convenient and fast. Furthermore, it allowed writing proxy functions which recognize parameters type and it runs the proper function. Yes, overriding doesn’t exist in PHP.

But things changed when I discovered polymorphism. The magic keyword Interface that allows us to define the mandatory set of features in the object. The picture of writing a code without worrying about missing methods was incredible. I still didn’t understand one thing yet. How interfaces help me since I can pass everything on the function call?

This question lied a long time in my head until I discovered I can define the required type of passed object. Since that time, I changed completely the way how I write the code.

My first interfaces weren’t a piece of art. They were ugly, big, and sometimes overloaded by methods. However, they changed completely my approach to building applications. With time and practice, I started creating better, more consistent and useful interfaces.

Unfortunately, PHP gave me a possibility to do nasty things. Something, what is completely unacceptable in statically typed languages.

Let me show you an example.

Imagine we have the ReadableInterface with could be anything where from we can read: stream, file, HTTP resource, whatever we want. Let’s say that we use one single implementation — ReadableFile and we need to store parsed data in the file with a recognized name. Method getFileName exists but only in this specific implementation.

public function handle(ReadableInterface $readable)
{
    /** @var ReadableFile $readable **/
    $fileName = $readable->getFileName();
    
    $parsed = $this->parse($readable);
    
    file_put_contents(__DIR__ . "/{$fileName}.parsed");
}

What did I do? I lied. I lied that we can pass to the function something that is readable. Linters are happy, PHP is happy, what could go wrong? Nothing, unless we pass to this method e.g. ReadableStream implementation which has no getFileName.

This long introduction was on purpose showing the problem with polymorphism with PHP. Is technically possible to use methods and properties from outside the interface. It doesn’t mean we should do it. We should never assume that methods or properties which are not defined by interface will exist within every implementation.

I share this story just to give some context to the problem. I also produced code like above but I stopped when I realized that is wrong. However, I sometimes notice that people confidently use methods from outside of interface just because they can and it’s easy. It’s not a good way.

What is the interface for?

Basically, the interface is:

  • set of features, that object must have
  • a contract that object delivers something but we don’t care which specific object

But the best explanation of Interface I found on StackOverflow. Consider this situation:

You are in the middle of a large, empty room, when a zombie suddenly attacks you.

You have no weapon.

Luckily, a fellow living human is standing in the doorway of the room.

“Quick!” you shout at him. “Throw me something I can hit the zombie with!”

You can read the full answer here.

What does the man ask about? About something that implements interface! He didn’t specify what exactly but he defined his needs.

What is the problem in PHP?

There is not a problem with PHP itself. Maybe a little bit. It will not stop you from using the methods or properties outside the interface.

But the problem lies in us, developers. If we always treat interface as a contract, nothing wrong will happen. However, we often know what is behind the interface and it’s a potential field for abuse. Instead of using “something, that… (behavior)” we use concrete implementation, even unconsciously. Because we can.

How to solve it?

Simply, treat the Interface as a contract. Use only methods which are defined by this contract. Don’t think what it exactly is. As long as it implements the Interface that you expect, it’s OK!

If you really want to use something outside the interface, you should revalidate its. Is it still relevant? Maybe it’s not so useful as it should be? Or maybe should you expect a specific implementation rather than Interface?

Summary

Needless to say, that polymorphism is a powerful tool. It is also one of the most important things in Object Oriented Programming paradigm. The described problem also involves abstract classes, not only interfaces.

There is one case where I decide to partially bypass the interface. Take a look at this example:

interface MessageInterface {...}
interface LoggableMessageInterface extends MessageInterface {...}

interface MailerInterface 
{
    public function sendTo(string $email, MessageInterface $message): void;
}

Imagine, that we want to create LoggableMailer which should be compatible with the MailerInterface. This mailer is able to handle both MessageInterface and LoggableMessageInterface but for the second once, it implements the extra behavior. The easiest way is to check whether passed $message is an instance of LoggableMessageInterface. I’m still looking for a better solution.

Generally speaking, we should use only methods and properties that are explicitly defined by the contract.

Resources

Featured photo by Igor Ovsyannykov on Unsplash.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.