Как то так
class UserData {
/**
* @readonly
* @var string
*/
public $source;
public function __construct(string $source)
{
$this->source = $source;
}
}
class NoSupportingSenderFound extends \Exception {}
class AnswerTransportError extends \Exception {}
interface AnswerSender {
public function supportSource(string $source): bool;
/**
* @throws AnswerTransportError
*/
public function sendAnswer(string $answer): void;
}
class AnswerSenderFacade
{
/**
* @var AnswerSender[]
*/
private $senders = [];
public function __construct(AnswerSender ... $answerSenders)
{
$this->senders = $answerSenders;
}
/**
* @throws AnswerTransportError
* @throws NoSupportingSenderFound
*/
public function sendAnswer(UserData $userData, string $answer)
{
foreach ($this->senders as $sender) {
if($sender->supportSource($userData->source)) {
$sender->sendAnswer($answer);
return;
}
}
throw new NoSupportingSenderFound("...");
}
}
В конструктор класса AnswerSenderFacade нужно передать инстансы всех реализаций интерфейса AnswerSender. В Symfony это просто делается через
tagged services (Навесить тег на _instanceof AnswerSender через конфиг и в конфиге же указать что все помеченные тегом классы нужно заинжектить)
Над названием конечно можно ещё подумать.
p.s. в случае Симфони, правда, в конструкторе вместо ...$providers придётся использовать iterable и дополнительно проверять что пришли инстансы SenderInterface через instanceof