Table of Contents
You have a method that accepts an interface type hint. Then a new caller needs to use the same method, but its object doesn’t implement that interface. The lazy fix? Widen the type hint to object and check with instanceof.
Here’s when that’s actually the right call.
The problem
Say you have a repository method that handles invalid configuration:
class ConfigRepository
{
public function handleInvalidConfig(
string $configKey,
SyncableInterface $handler
): void {
$mapping = $handler->getConfigMapping($configKey);
$mapping->markInvalid();
$mapping->save();
}
}
This works great — until a new type of handler comes along that doesn’t implement SyncableInterface but still needs to report invalid configuration. The handler doesn’t have config mappings at all; it just needs the “mark as invalid” side of the logic.
You get a TypeError:
ConfigRepository::handleInvalidConfig(): Argument #2 ($handler) must be of type SyncableInterface, BasicHandler given
The fix: widen and guard
class ConfigRepository
{
public function handleInvalidConfig(
string $configKey,
object $handler
): void {
if (!$handler instanceof SyncableInterface) {
// This handler doesn't support config mapping — skip gracefully
return;
}
$mapping = $handler->getConfigMapping($configKey);
$mapping->markInvalid();
$mapping->save();
}
}
When this pattern makes sense
This isn’t “make everything object and pray.” It’s appropriate when:
- The caller hierarchy is fixed — you can’t easily make all callers implement the interface (e.g., the method is called from a shared base class)
- The behavior is optional — not all callers need the full logic, some just need to pass through
- The alternative is worse — duplicating the method or adding a parallel code path creates more maintenance burden
The alternative: multiple interfaces
If you find yourself widening type hints often, it might signal that your interface is doing too much. Consider splitting:
interface ReportsInvalidConfig
{
public function getConfigMapping(string $key): ConfigMapping;
}
interface SyncableInterface extends ReportsInvalidConfig
{
public function sync(): void;
}
// Now the method can type-hint the narrow interface
public function handleInvalidConfig(
string $configKey,
ReportsInvalidConfig $handler
): void {
$mapping = $handler->getConfigMapping($configKey);
$mapping->markInvalid();
$mapping->save();
}
This is cleaner long-term, but requires changing the interface hierarchy — not always possible in legacy codebases or when you’re shipping a hotfix at 2 AM.
The pragmatic rule: object + instanceof for hotfixes and optional behavior. Interface segregation for planned refactors. Both are valid — just know which situation you’re in.

Leave a Reply