Best practice to implement Factory pattern using Symfony2
Asked Answered
G

1

7

I am making a messenger which can send email messages or sms messages, and has the possibility to send them now or send them later (the information is saved in the DB). I've made 2 solutions, but neither is satisfying me.

I'm centralising the code in one Factory, and the code of the Factory pattern is very easy:

class MessageFactory
{
     static public function get($type,$em)
     {
          $instance = null;
          switch ($type) {
              case 'email':
                    $instance = new EmailMessage($em);
                    break;
   ....
  return $instance;
}

class EmailMessage implements MessangerInterface
{
 ...
   public function send( $eMessage,array $receivers, $time=NULL)
   {

interface MessangerInterface
{
  public function send($message,array $receivers);
}

1st solution: Just call as an ordinary static method

$messanger = Factory\MessageFactory::get('email',$em);
$messanger->send($eMessage, array('tom'=>'[email protected]'));

This is a bad solution, because I need to pass in a Doctrine Manager as a parameter to the method

2nd solution: To use it as a Symfony 2 Service

services:
my.messanger:
    class: Bundle\Factory\MessangerInterface
    factory_class: Bundle\Factory\MessageFactory
    factory_method: get
    arguments:
        messanger_type: %messanger.type%

and also pass in Doctrine as an argument. But using such a solution I can't choose messanger.type in my code, it's defined using a configuration parameter as email or sms; I need to have the capability in code to choose the type.

Also I have a problem that inside the class I need to send email or sms, and that means that I need an external service, getting it like this:

class EmailMessage implements MessangerInterface
{
 if ('AppCache' == get_class($kernel)) {
      $kernel = $kernel->getKernel();
   }
 $kernel->getContainer()->get('mailer')->send($eMessage);

which seems like very bad practice.

Please, are you able to advise me on any better solutions?

I want to follow the "thin controller fat model" concept.

Gray answered 30/9, 2014 at 9:35 Comment(0)
H
6

It seems like option 2, using Symfony 2 Services, would be best.

I considered suggesting that you let the Factory be the Service, and pass the type in to get the Messenger instance, rather than fixing it in config, but if what you want is to only have one of each type of Messenger then that's unhelpful (the Factory would keep creating more and more Messengers). So instead I think you need to define two Services, one for each Messenger.

And if you don't want to have to fetch another Service within your Messenger, you need to inject that in when you get the Messenger.

e.g.

services:
    mailer:
        class: Mailer
    smser:
        class: SMSer

    email.messanger:
        class: Bundle\Factory\MessangerInterface
        factory_class: Bundle\Factory\MessageFactory
        factory_method: get
        arguments:
            messanger_type: email
            sender: @mailer
    sms.messanger:
        class: Bundle\Factory\MessangerInterface
        factory_class: Bundle\Factory\MessageFactory
        factory_method: get
        arguments:
            messanger_type: sms
            sender: @smser

And your Factory needs to accept the new $sender argument:

class MessageFactory
{
     static public function get($type,$em,$sender)
     {
          $instance = null;
          switch ($type) {
              case 'email':
                    $instance = new EmailMessage($em, $sender);
                    break;
   ....
  return $instance;
}

interface MessangerInterface
{
    public function send($message,$sender, array $receivers);
}

Then when you call it, you ask for either of the Messengers specifically:

$this->get('email.messenger')->send($emailMessage);
$this->get('sms.messenger')->send($smsMessage);
Hill answered 30/9, 2014 at 12:36 Comment(4)
Perfect ! but how does it work if i have a factory of a propel object, am i supposed to declare the propel object as a service ?Brevity
What do you mean, "factory of a propel object"? Your factory creates a propel object, or something else?Hill
Lets say that EmailMessage or SmsMessage are propel object, i must declare them as service right ? If i have a project with 50 objects, i must declare all of them as services , isnt there a way to do this easily ?Brevity
Defining things as services allows you to ensure that there's only one of them, access them globally, and also provide them to other services automatically. It's been a while since I've done Propel, but you don't have Repositories or Entity Managers do you, you just have Entities and static methods? So I'd be tempted to say that you don't need to define any additional services to work with Propel, you just create objects or use the static methods within your code. I'd ask a separate question if you need to, though.Hill

© 2022 - 2024 — McMap. All rights reserved.