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.
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.
There are several alternative fixes available for this issue:
Change the type-hint for the parameter:
class AuthSystem
{
public function authenticate(MyUser $user) { /* ... */ }
}
Add an additional type-check:
class AuthSystem
{
public function authenticate(User $user)
{
if ($user instanceof MyUser) {
$this->logger->info(/** ... */);
}
}
}
Add the method to the interface:
interface User
{
/** @return string */
public function getPassword();
/** @return string */
public function getDisplayName();
}