Фабричный шаблон в PHP на основе строки (ООП)

У меня есть следующий сценарий:

Пользователь отправляет методу фабрики URI, который представляет полную информацию для соединения с классом MAILER. Например: «smtp: // пользователь: пароль @ сервер: порт». Я разбил эту строку подключения на части, которые представляют «протокол», «имя пользователя», «пароль», «почтовый сервер», «порт» и так далее. С протоколом я получаю экземпляр почтовой программы, который будет отвечать за отправку электронной почты. Весь экземпляр Mailer реализует MailWrapperInterface.

Сегодня я делаю следующее:

/**
* @return MailWrapperInterface
*/
public static function mailerFactory($protocol): MailWrapperInterface
{
if (in_array($protocol, ['smtp', 'ssl', 'tls'])) {
$mail = new PHPMailerWrapper($connection);
} elseif ($protocol === "ses") {
$mail = new AmazonSesWrapper($connection);
} elseif ($protocol === "mandrill") {
$mail = new MandrillApiWrapper($connection);
} elseif ($protocol === "sendmail") {
$mail = new SendMailWrapper($connection);
} elseif ($protocol === "mailgun") {
$mail = new MailgunApiWrapper($connection);
} else {
throw new InvalidArgumentException("The $protocol is not valid");
}

return $mail;
}

Но я не чувствую себя комфортно с этой реализацией, потому что:

  • Код очень связан
  • Я не могу иметь новые реализации без изменения метода фабрики

Во всяком случае, я знаю, что это так неправильно! Но каков наилучший способ сделать этот фабричный метод без проблем, описанных выше (и других?)

2

Решение

В целом ваши решения лучше, чем вы думаете. Задача фабрики — перенести создание экземпляров классов на некоторый ресурс, который может быть изолирован от вашей кодовой базы.

Но есть большая проблема с вашим кодом: ваша фабрика статична. Это означает, что у вас нет способа фактически заменить или расширить его, не переписывая каждый второй фрагмент кода, где он использовался.

Что касается этого уродливого if-else Блок, как уже упоминал Rasclatt, решение состоит в том, чтобы просто использовать файл конфигурации.

class MailerFactory {

private $config = [];

public function __construct($config) {
$this->config = $config;
}

public function create($uri) {
$parts = $this->splitProtocolFunctionSomething($uri);
$protocol = $parts['protocol'];
$connection = $this->makeTheConnectionSomehow();

if (!array_key_exists($protocol, $this->config)) {
throw new InvalidArgumentException("The $protocol is not valid");
}

$class = $this->config[$protocol]['classname'];
return new $class($connection)
}
}

Тогда вы просто используете это, написав:

$factory = new MailerFactory(jons_decode('/path/to/mailers.config.json'));
$mailer = $factory->create('smtp');

Таким образом, вы можете обойти эту фабрику как зависимость от всех экземпляров в вашем коде, которым это потребуется. И, при написании модульных тестов, вы можете заменить эту фабрику на какую-то ложную или двойную проверку.

Альтернативный подход

У вас также могут быть другие варианты здесь. Вы можете переписать свой код, чтобы не зависеть от заводов, а вместо этого использовать DI-контейнер (например, Auryn или же Symfony DI).

Таким образом, код, которому нужны ваши почтовые программы, уже будет иметь пригодный для использования код, передаваемый в конструктор, когда он создается.

Причина, по которой я поднимаю эту альтернативу, заключается в том, что с того места, где я стою, похоже, что ваши упаковщики могут в конечном итоге потребовать другие параметры. И в этом случае использование фабрики становится несколько .. эмм … громоздким.

3

Другие решения

В таких ситуациях я иногда прибегаю к внешним файлам pref или решению для базы данных. Зависит от того, сколько раз необходимо вызвать данные.

Я хотел бы иметь файл, который может быть отредактирован приложением или вручную, что-то вроде XML-файла. Он будет хранить все настройки и легко редактировать для добавления или удаления протоколов. Таким образом, вам не нужно менять метод через расширение класса или жестко закодированный if/elseifВы просто обновляете файл настроек.

/core/prefs/protocols.xml

<?xml version="1.0" encoding="UTF-8"?>
<config>
<protocol classname="PHPMailerWrapper">
<arg>smtp</arg>
<arg>ssl</arg>
<arg>tls</arg>
</protocol>
<protocol classname="AmazonSesWrapper">
<arg>ses</arg>
</protocol>
<protocol classname="MandrillApiWrapper">
<arg>mandril</arg>
</protocol>
</config>

Измените метод, который вы используете в настоящее время

# I would add this function to fetch that file and convert it to object
private static function getEngines()
{
return simplexml_load_file('core/prefs/protocols.xml');
}
# I would alter this method
public static function mailerFactory($protocol)
{
# Get xml prefs
$XMLEngine  =   self::getEngines();
# Loop through protocols
foreach($XMLEngine as $obj) {
# Check if protocol in current object
if(!in_array($protocol,json_decode(json_encode($obj->arg),true)))
continue;
# Get class name arra
$classArr   =   (array) $obj->attributes()->classname;
# Set name
$class      =   $classArr[0];
# Create variabled class
return new $class($connection);
}
# Throw if all else fails...
throw new InvalidArgumentException("The $protocol is not valid");
}

Во всяком случае, это просто идея. Я делаю это много для автоматизации моих веб-приложений.

1

Я предлагаю превратить фабрику в класс с помощью открытого статического метода getMailer (). Этот метод вызывает частные статические методы, названные в честь поддерживаемых протоколов, и возвращает их результат. Метод getMailer может проверить, существует ли метод в классе, названном для протокола input $, и, если нет, вызвать исключение. Для поддержки новых протоколов просто добавьте соответствующие частные статические методы в класс.

0
По вопросам рекламы [email protected]