Understanding self vs static in PHP: Why Your Interface Implementation May Fail

Understanding self vs static in PHP: Why Your Interface Implementation May Fail
Photo by Roberto Nickson / Unsplash

When working with interfaces and return types in modern PHP (especially PHP 8.0 and PHP 8.1+), you might run into a confusing but very real error:

Fatal error: Declaration of B::new(): B must be compatible with A::new(): static

This error trips up many developers because it seems like you're doing everything right. After all, self and static often feel interchangeable. But PHP's type system sees them very differently—especially in the context of interfaces and inheritance.


The Root of the Problem

Let's start with an example:

interface A
{
    public static function new(): static;
}

final class B implements A
{
    public static function new(): self // ❌ Fatal error
    {
        return new self();
    }
}

You may think that since B is final, there’s no way static and self can differ. True at runtime, but not true at compile time.


staticself

Here’s the key difference:

  • self refers to the class where the method is declared.
  • static uses late static binding, which means it refers to the class that is called, even in a child class.

Even in a final class, PHP treats self and static as distinct types in terms of method signatures. That’s why using self where static is expected causes a fatal error.


The Correct Fix

To resolve the error, update the return type to match the interface exactly:

final class B implements A
{
    public static function new(): static // ✅ Now valid
    {
        return new static();
    }
}

Even though the class is final, type declarations must align exactly with the interface definition. PHP won't assume self is “good enough.”


Why PHP Is Strict Here

PHP’s strictness ensures that:

  • Interfaces can be relied upon: other code using the interface can safely expect the return type to be what was promised.
  • Inheritance behaves correctly: if someday the class is not final, or if A is reused by another class, it still works.

Even when a class is final, PHP doesn't relax these rules to avoid accidental breaking changes later.


Bonus: Other Considerations

  • Trait methods also must match return types when implementing an interface indirectly.
  • Covariant return types (introduced in PHP 7.4) allow child classes to return more specific types, but not less specific.
  • Abstract classes must follow the same contract rules as interfaces regarding static return types.

Finally

To avoid frustrating fatal errors:

  • Always match interface return types exactly—even if it seems redundant.
  • Never substitute self for static when the contract explicitly says static.
  • Use static in return types when you want to support late static binding.

Understanding the subtleties between self, static, and even parent in PHP will help you write more robust, future-proof, and contract-safe code.

Support Us