Interfaces in PHP don’t make complete sense

There is no way to make sure that all the clients implementing the interface will return the correct type (or even return at all), the only thing I can think of is testing our classes behavior before writing the code.

TTD FTW!Addressing these problems with PHP 7Fortunately we don’t have to live in the past anymore, we can rewrite our ParserInterface interface and JSONParser class to make them more object-oriented.

Let’s see.

interface ParserInterface{ public function parse(array $data): string;}class JSONParser implements ParserInterface{ public function parse(array $data): string { $parsed = json_encode($data); if ($parsed == false) { throw new UnparseableException('Sorry we can't parse your data'); } return $parsed; }}With these simple changes we can get rid of the guard clause used to verify the argument type, it’s a win-win!But we still have a problem, this code is not very object-oriented, passing an array to the parse method feels kinda messy, it would be nicer if we can pass an object of type, let’s say Format.

Let’s make it better.

interface ParserInterface{ public function parse(Format $data): string;}abstract class Format{ protected $value; public function __construct(array $value) { $this->value = $value; } public function getValue(): array { return $this->value; }}final class JSON extends Format{ const FORMAT = 'JSON';}In theory we should only implement ParserInterface in JSONParser, and everything will work just fine.

Well, not exactly.

class JSONParser implements ParserInterface{ public function parse(JSON $JSONData): string { $parsed = json_encode($JSONData->getValue()); if ($parsed == false) { throw new UnparseableException('Sorry we can't parse your data'); } return $parsed; }}If we run this…Fatal error: Declaration of JSONParser::parse(JSON $JSONData): string must be compatible with ParserInterface::parse(Format $data): string in.

What!?.How can this be possible?.Why is this failing?.Well, time to see the current and most important issue with interfaces in PHP.

The current problem with interfaces in PHP: lack of covariance and contravarianceCovariance and contravariance are fancy terms to describe really simple concepts, if you use them your boss will think you’re smarty pants and maybe will give you a raise, you lose nothing by trying.

Contravariance (or simply variance) is the ability to replace an object with its parent without breaking the implementation.

Covariance is the opposite, the ability to replace an object with one of its children, without breaking the implementation.

– What does this mean and how is it related to interfaces in PHP?Pretty simple!.PHP doesn’t support contravariance and/or covariance, nor in interfaces, neither in classes.

That’s the reason why we obtain a fatal error when trying to implement ParserInterface and change the type hinting from Format (abstract class) to JSON (concrete class).

Just for demonstration purposes, let’s see how we can implement the same logic using a more object-oriented language, in this case, C#.

public interface IParser <out T> where T : Format{ T Parse(string data);}public abstract class Format{ protected string value; public Format(string value) { this.

value = value; } public string GetValue() { return value; }}public class JSON : Format{ public JSON(string value) : base(value) { } public string FORMAT = "JSON";}public class JSONParser : IParser<JSON>{ public JSON Parse(string JSONString) { return new JSON(JSONString); }}Here, we’re defining an interface with a generic parameter, we can pass any Format’s child class to it, and voilá, everything will work as expected.

If you don’t totally understand the syntax in the C# implementation that’s fine, after all, this post is about PHP, just remember that this works exactly as we want, nice!So, the real question remains: How do we address this problem in PHP?.Well, we can’t… or can we?PHP 7.

4 FTW!We already talked about the past and present of PHP, now let’s talk about the future!A new RFC has been approved to introduce covariance and contravariance support in PHP 7.

4 (hallelujah!), without the need for generic, in/out notations, and all that weird stuff we need to make use of in other languages like C#.

You can see the details here, and if you are curious enough, the implementation here, so unless something exceptional happens we will be enjoying this feature in the next release.

So, assuming we have the PHP 7.

4 runtime, our last implementation should work as expected!class JSONParser implements ParserInterface{ public function parse(JSON $JSONData): string { $parsed = json_encode($JSONData->getValue()); if ($parsed == false) { throw new UnparseableException('Sorry we can't parse your data'); } return $parsed; }}## somewhere_in_your_application.

php$JSON = new JSON(['name' => 'Max', 'languages' => ['PHP', 'C#', 'JavaScript']]);$parser = new JSONParser();try { $JSONString = $parser->parse($JSON);} catch (UnparseableException $exception) { // Let's suppose we are handling the exception properly}// $JSONString is equal to {"name":"Max","languages":["PHP","C#","JavaScript"]}!Smooth!Post-creditsI have been programming in PHP for more than 5 years, (I know, it’s not that long, but that’s my journey!) and until now, this is by far my favorite addition to the language, once you’ve worked in other languages that already implement this feature you start loving it.

PHP 7.

4 will be a great release overall, let’s wait for it!Before closing I have to say, the RFC defines a lot more than what we saw, so if you want to know in detail how covariance and contravariance are supposed to work in PHP 7.

4, you should definitively check the RFC.

As always I want to remind the reader that the code written here is only for demonstration purposes and most of the time I end up using really bad examples; you could say — why the hell aren’t you just using json_encode to parse your damn array!?- and well, you’re right, but you get the point of the article, don’t you?I hope you have found this article useful.

Thanks for staying this long!Originally posted in devalmonte.


. More details

Leave a Reply