Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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)?

like image 985
FtDRbwLXw6 Avatar asked Oct 22 '22 13:10

FtDRbwLXw6


2 Answers

I think I got a way to do this. It is more a trick than a really clean design thing but I think I got the point.

abstract class Foo {
private $bazBehaviour;
    public function __construct($bazBehaviour){
        $this->bazBehaviour=!empty($bazBehaviour)?$bazBehaviour:"defaultBazBehaviour";
    }
   final public function baz() {
       $this->bazBehaviour();
   }
   final protected function defaultBazBehaviour(){
        echo "Foo::Baz()";
    }
   // other methods here
}

abstract class Bar extends Foo {
    public function __construct(){
       parent::__construct("bazBehaviour");
    }
    final protected function bazBehaviour() {
        echo 'Bar::baz()';
     } 
}
class toto extends Bar{
     public function __construct(){
       parent::__construct();
    }
}
$d = new toto();
$d->baz();

with PHP 5.3.13 it sends : Bar::Baz()

like image 50
artragis Avatar answered Oct 27 '22 10:10

artragis


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.

like image 22
nairbv Avatar answered Oct 27 '22 10:10

nairbv