Search code examples
phpoopfactory-pattern

Factory pattern in PHP based on a string (OOP)


I have the follow scenario:

The user send to the factory method the URI that represents the full information to connect to a MAILER class. For example: "smtp://user:password@server:port". I split this connection string into parts that represents the "protocol","username", "password", "mail server", "port" and so on. With the protocol I get the mailer instance that will be responsible to send the email. All Mailer instance implements the MailWrapperInterface.

Today I do the follow:

/**
 * @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;
}

But I am not feel comfortable with this implementation because:

  • The code is very coupled
  • I cannot have new implementations without change the factory method

Anyway, I know this is so wrong! But what is the best way to do this factory method without the problems above (and others?)


Solution

  • As a whole, your solutions is better than you think. The point of a factory is to shift the instantiation of classes to some resource, that can be isolate from your codebase.

    But there is a big problem with your code: your factory is static. That means, that you have no way to actually replace it or extend it without rewriting every other piece of code, where it has been used.

    As for that ugly if-else block, as Rasclatt already mentioned, the solution is simply to use a configuration file.

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

    Then you just use it by writing:

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

    This way you can pass around this factory as a dependency to all the instances in your code, that will require it. And, when writing unit tests, you can replace this factory with some mock or test double.

    Alternative approach

    You also might have a different options here. You could rewrite your code so that you do not depend on factories, but instead use DI Container (like Auryn or Symfony DI).

    That way the code, that needs your mailers, would already have a usable one being passed in the constructor, when it is instantiated.

    The reason why I am raising this alternative is that, from where I stand, it looks like your wrappers might end up requiring different parameters. And in that case using a factory becomes somewhat .. emm .. unwieldy.