Указанные тобой варианты имеют право на существование, также я бы дополнил список
5. использовать Proxy объект
6. Использовать di и на основе настроект di подсовывать реализацию и параметры из конфигов
Например у твоего MailServer есть interface MailServerInterface от который будет использоваться тобой везде, у тебя есть эталонная реализация MailServer и тебе надо сделать логирование методов.
```php
MailServerProxy implements MailServerInterface {
public function __construct (LoggerInterface $logger, MailServerInterface $server) {
$this->setLogger($logger);
$this->setMailServer($server);
}
public function __call($name, $args) {
$this->logger->info('called method ' . $name);
return call_user_func_array([$this->mailServer, $name], $args);
}
}
```
Примерный шаблон кода.
Тогда там где нужен MailServerInterface его будет создавать di, а уже di в зависимости от настроек системы будет создавать зависимости, например если надо логировать, то будет создавать MailServerProxy которому аргументами нужен Logger(тут любые параметры могут быть) и MailServer (а ему нужны аргументы $host, $login, $password которые в конфиге лежат и их читать будет di)
А в случае если логирование не нужно станет, достаточно в настройках di при запросе MailServerInterface создавать MailServer сразу, ну или использовать NullLogger какой нибудь который ничего не пишет.
также есть еще аспектно ориентированное программирование
https://github.com/goaop/framework
но оно работает медленнее так как надо прочитать анотации через reflection но тоже может быть не плохо.