rfc:abstract_trait_method_validation

PHP RFC: Validation for abstract trait methods

Introduction

Traits can contain abstract methods, which are used to specify requirements the trait has on the using class. However, the signatures of these methods are currently only erratically enforced.

This RFC proposes to verify abstract trait methods against the implementation provided in the using class, according to our usual inheritance rules. Additionally the RFC allows the declaration of abstract private methods in traits.

trait MyTrait {
    abstract private function neededByTheTrait(): string;
 
    public function doSomething() {
        return strlen($this->neededByTheTrait());
    }
}
 
class TraitUser {
    use MyTrait;
 
    // This is allowed:
    private function neededByTheTrait(): string { }
 
    // This is forbidden (incorrect return type)
    private function neededByTheTrait(): stdClass { }
 
    // This is forbidden (non-static changed to static)
    private static function neededByTheTrait(): string { }
}

Proposal

Status Quo

Abstract trait method signatures are currently not enforced in likely the most common case, where the method implementation is provided directly by using class:

trait T {
    abstract public function test(int $x);
}
 
class C {
    use T;
 
    // Allowed, but shouldn't be due to invalid type.
    public function test(string $x) {}
}

The method signature is enforced if the implementing method comes from a parent class:

trait T {
    abstract public function test(int $x);
}
 
class P {
    public function test(string $x) {}
}
 
class C extends P {
    use T;
 
    // Not allowed, because P::test() incompatible with T::test().
}

The method signature is also enforced if the implementing method comes from a child class:

trait T {
    abstract public function test(int $x);
}
 
abstract class P {
    use T;
}
 
class C extends P {
    // Not allowed, due to type mismatch.
    public function test(string $x) {}
}

Finally, if multiple traits define the same abstract method, then the signature is enforced bidirectionally between the traits (but not the implementing class):

trait T1 {
    abstract public function test($x);
}
 
trait T2 {
    abstract public function test($x): int;
}
 
class C {
    // Invalid, because T1::test() incompatible with T2::test().
    use T1, T2;
 
    public function test($x): int {}
}

This enforcement is incorrect: The method implementation provided by the class is compatible with both traits. As such, this should be legal code.

Proposal

This RFC proposes to always validate the signature of abstract trait methods against the implementing method, independently of its origin. Additionally the incorrect bidirectional cross-trait compatibilty check from the last example is removed.

A fatal error is generated if the implementing method is not compatible with the abstract trait method, where compatibility entails:

  • The signature must be compatible, which includes arity compatibility, contravariant parameter type compatibility and covariant return type compatibility.
  • The static-ness of the methods must match.

Additionally, this RFC allows the declaration of abstract private methods in traits only. Normally abstract private methods are a contradiction in terms, because the method providing the implementation would not be visible from the class issuing the requirement. However, abstract private methods are well-defined inside traits, because trait methods have access to private methods of the using class.

Private abstract methods must be implemented by the using class. Their implementation cannot be postponed by marking the class abstract, as this would once again render the implementation inaccessible.

Contrary to the usual inheritance rules, the visibility level of the abstract trait method is not enforced. This means that an abstract protected method in the trait can be implemented by a private method in the class, even though this reduces visibility. This exception is added for backwards-compatibility reasons: Because abstract private methods were forbidden prior to this proposal, a private requirement for the trait could not be specified with private visibility. This exception allows code to use abstract protected trait methods with private implementations and thus be compatible with both PHP 7 and PHP 8. Code that only needs to be compatible with PHP 8 should prefer the use of abstract private methods for clarity.

Backward Incompatible Changes

Code that currently declares abstract trait methods and implements them with an incorrect signature in a using class will break. Such code can be fixed by either fixing the method signature in the using class, or by removing the abstract method from the trait.

Vote

Voting opened 2020-03-06 and closes 2020-03-20.

Validate abstract trait methods?
Real name Yes No
ajf (ajf)  
alcaeus (alcaeus)  
alec (alec)  
as (as)  
ashnazg (ashnazg)  
beberlei (beberlei)  
brzuchal (brzuchal)  
bwoebi (bwoebi)  
carusogabriel (carusogabriel)  
cmb (cmb)  
colinodell (colinodell)  
daverandom (daverandom)  
derick (derick)  
diegopires (diegopires)  
ekin (ekin)  
galvao (galvao)  
girgias (girgias)  
googleguy (googleguy)  
jhdxr (jhdxr)  
kalle (kalle)  
kguest (kguest)  
kocsismate (kocsismate)  
lcobucci (lcobucci)  
levim (levim)  
malukenho (malukenho)  
marandall (marandall)  
mariano (mariano)  
mcmic (mcmic)  
narf (narf)  
nicolasgrekas (nicolasgrekas)  
nikic (nikic)  
ocramius (ocramius)  
pmjones (pmjones)  
pmmaga (pmmaga)  
pollita (pollita)  
ralphschindler (ralphschindler)  
ramsey (ramsey)  
rasmus (rasmus)  
reywob (reywob)  
ruudboon (ruudboon)  
santiagolizardo (santiagolizardo)  
sebastian (sebastian)  
sergey (sergey)  
sirsnyder (sirsnyder)  
stas (stas)  
svpernova09 (svpernova09)  
tandre (tandre)  
trowski (trowski)  
villfa (villfa)  
wyrihaximus (wyrihaximus)  
yunosh (yunosh)  
zimt (zimt)  
Final result: 52 0
This poll has been closed.
rfc/abstract_trait_method_validation.txt · Last modified: 2020/03/23 14:06 by nikic