Search code examples
phpinheritanceoverridingfinal

Almost-final methods in PHP?


I have two abstract classes in an inheritance chain, within what will eventually be a generic library:

abstract class Foo {
    public function baz() {
        echo 'Foo::baz()';
    }

    // other methods here
}

abstract class Bar extends Foo {
    public function baz() {
        echo 'Bar::baz()';
    }
}

These two classes are meant to be extended by developers, and my problem is that I'd like to make it so that neither implementation of the baz() method can be overridden (as they contain strict RFC-compliant code). Making Bar::baz() final is no problem; however, if I make Foo::baz() final, then Bar itself obviously can't override it either.

PHP 5.4's traits would likely offer a practical solution, but I can't drop support for PHP < 5.4 over this. My last resort is to just leave it as-is and use documentation to warn developers not to override this method, but I'd like to find something more concrete, if possible.

Is there any other design I can use to enforce that both methods shouldn't be overridden, while simultaneously keeping the code DRY (e.g. not removing the inheritance and duplicating all the code)?


Solution

  • This seems like a situation where the idea "Favor composition over inheritance" applies. It's a little bit repetitive, but it doesn't involve repetition of implementations and gives you the functionality you want.

    interface Bazr {
        public function baz();
    
        public function myOtherMethod1();
        public function myOtherMethod2();
    }
    
    public class Foo implements Bazr {
      public final function baz() {} 
      public function myOtherMethod1() {/* default implementation of some method */}
      public function myOtherMethod2() {/* yeah */}
    }
    
    public class Bar implements Bazr {
      private parentBazr = null;
      public function __construct() {
        $this->parentBazr = new Foo();
      }
    
      public final function baz() {}
      public function myOtherMethod1() {
        $this->parentBazr->myOtherMethod1();
      }
      public function myOtherMethod2() {
        $this->parentBazr->myOtherMethod1();
      }
    }
    

    Both Foo and Bar can be extended, Bar "composes" Foo's functionality, and baz is final in both Foo and Bar. I'm not sure if you prefer to include or omit the interface... PHP doesn't have typed variables so it's probably not necessary besides for enforcing the contract with the two implementing classes. The disadvantage of defining the interface is that others can then implement the interface (though that would also be the case with a trait).

    Sorry if I mixed java/php syntax, I think I left out some "$"'s.