Search code examples
phpinterfaceconstructortypecheckingmodel-checking

Is it possible to (somehow?) declare the format of a constructor in a PHP interface (or anything about it)?


I would like some feedback on my coding approach (i.e., whether it is appropriate or whether what I have done can be done in a perhaps better way):

I would like to create an interface to document that a constructor should have a specific format. Of course, if the interface only contains a constructor (and I was even surprised that PHP lets you put a constructor in an interface), the interface will have no effect (except for possibly documentation). Besides, PHP does not enforce the parameters of any callable to match, neither in number nor in type, and this is true of functions, methods, and constructors alike.

If you see how I have named my classes, you will realize what I am trying to do (: document that the constructor parameter must be a messager instance, too bad I could not do more to enforce this). Please let me know if my approach is OK and whether I can improve it.

class Messenger {

  private $message;

  function __construct($message = "Hello!") {

    $this->message = $message;

  }

  public function getMessage() {

    return $this->message;

  }

}

With the above simple class in mind, I want to create an interface such as the following, but since we're dealing with a PHP constructor this should be useless?

interface MessengerAware {

  function __construct($messenger);

}

class MessengerKnower implements MessengerAware {

  private $messenger;

  function __construct($messenger) {

    $this->messenger = $messenger;

  }

  public function displayMessengerMessage() {

    echo $this->messenger->getMessage();

  }

}

I then want to enforce my interface in a class called Runner such as the following:

class Runner {

  private $messengerAware;

  function __construct($messengerAware) {

    if (!is_a($messengerAware, 'MessengerAware')) {

      die("I'm expecting an instance implementing the MessengerAware interface.");

    }

    $this->messengerAware = $messengerAware;

  }

  public function run() {

    echo "I'm running.\n";

    $this->messengerAware->displayMessengerMessage();

  }

}

and finally run this code:

$messengerAware = new MessengerKnower(new Messenger());
$runner = new Runner($messengerAware);
$runner->run();

OUTPUT:

I'm running.
Hello!

Solution

  • Perhaps it's not possible, but the problem could be worked around using one (or more) factory methods:

    Leave this unchanged:

    class Messenger {
    
      private $message;
    
      function __construct($message = "Hello!") {
    
        $this->message = $message;
    
      }
    
      public function getMessage() {
    
        return $this->message;
    
      }
    
    }
    

    This modification...

    interface MessengerAware {
    
      public static function create($messenger);
    
      public function displayMessengerMessage();
    
    }
    

    and this one...

    class MessengerKnower implements MessengerAware {
    
      private $messenger;
    
      public static function create($messenger) {
    
        $messengerKnower = new MessengerKnower();
    
        $messengerKnower->messenger = $messenger;
    
        return $messengerKnower;
    
      }
    
      public function displayMessengerMessage() {
    
        echo $this->messenger->getMessage();
    
      }
    
    }
    

    Leave this unchanged...

    class Runner {
    
      private $messengerAware;
    
      function __construct($messengerAware) {
    
        if (!is_a($messengerAware, 'MessengerAware')) {
    
          die("I'm expecting an instance implementing the MessengerAware interface.");
    
        }
    
        $this->messengerAware = $messengerAware;
    
      }
    
      public function run() {
    
        echo "I'm running.\n";
    
        $this->messengerAware->displayMessengerMessage();
    
      }
    
    }
    

    Finally adjust this code:

    $messengerAware = MessengerKnower::create(new Messenger());
    $runner = new Runner($messengerAware);
    $runner->run();
    

    OUTPUT:

    I'm running.
    Hello!