January 10 Johannes Schmitt schmittjoh

Preventing Coding against Concrete Implementations with PHP Analyzer

When you abstract a general concept which can be implemented in different ways, you usually encapsulate the concept in an interface. As PHP has no type system itself however, it is still possible to call methods on an object which are not part of the interface, yet are part of the concrete implementation.

This is generally unproblematic unless someone actually wants to pass a different implementation of the interface in which case your code will break. Those errors are rarely catched by tests, but PHP Analyzer helps you to detect them early.

An Example

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

There are several alternative fixes available for this issue:

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    

 

Have Feedback? Tweet to @scrutinizerci

If you experienced a bug or have any questions, please send them to [email protected].